ながめも

競技プログラミングについて

GCJ2019 B - Draupnir

問題概要

 x日リングは、 x日ごとに2倍になる。最初、 0日目に i日リングを R_i個( 1 \le i \le 6,  0 \le R_i \le 100)持っている。ただし、 R_iの少なくとも一つは 1以上である。

あなたは K回質問できる。一回の質問で、 D日( 1 \le D \le 500)のリングの所持数の合計を知ることができるので、 R_iを調べよ。ただし、所持数の合計は \mod 2^{63}である。

  • Small

 K = 6

  • Large

 K = 2

解説

Small

6回質問できるので、1 ~ 6日目を聞いて、連立方程式を解けばよい。手で解くのは無理なので、WalfmanAlphaなどを使って掃き出し法で解いてもらう。

結果のリンク

f:id:coonevo:20200419162843p:plain

  • コード
#include <bits/stdc++.h>
using namespace std;
#define rep(i,N) for(int i=0;i<int(N);++i)
template<class T> inline bool chmax(T& a, T b) { if (a < b) { a = b; return true; } return false; }
template<class T> inline bool chmin(T& a, T b) { if (a > b) { a = b; return true; } return false; }

int t, w;
int main() {
    cin.tie(0);
    ios::sync_with_stdio(false);

    cin >> t >> w;
    while(t--){
        ll D[7] = {};
        //interact w times
        for(int i = 1; i <= w; i++){
            cout << i << endl;
            cin >> D[i];
        }
        int a[][8] = {
            {0,   0,   0,   0,  0,  0,  0,   0},
            {0,   4,  -4,  -2,  0,  0,  1,  40},
            {0, -24,  24,   2,  0,  0, -1,  20},
            {0,  -4,  -6,  12,  0,  0, -1,  10},
            {0,  16, -16,  -8, 10,  0, -1,  10},
            {0,  -8,   8,   4, -5,  5, -2,   5},
            {0,  12,  -2,  -6,  0, -5,  3,   5},
        };
        //calc
        ll ans[7] = {};
        for(int i = 1; i <= 6;i++){
            for(int j = 1; j <= 6;j++) ans[i] += a[i][j] * D[j];
            ans[i] /= a[i][7];
        }
        //output
        for(int i = 1; i <= 6; i++){
            cout << ans[i];
            if(i == 6)cout << endl;
            else cout << " ";
        }
        //last input
        int valid;
        cin >> valid;
        if(valid == -1)return 0;
    }
}

Large

2回しか質問を取れないので、どうにかうまい質問で情報を取りたい。

 x日の情報は、以下のように表される。

\displaystyle  \sum_{i = 1}^{6}2^{\lfloor \frac{x}{i} \rfloor}*R_i

 2^{\lfloor \frac{x}{i} \rfloor}を乗算するということは、bit演算で考えると、 2^{\lfloor \frac{x}{i} \rfloor}個の左シフトである。 i 今回、 R_i \le 100なので、 R_iのbit表現は 7つの連続するbitで表現されている。よって、それぞれの値が 7個以上シフトが離れていれば独立に取り出せるということである。

また、質問で返ってくる値は \mod 2^{63}なので、 63回以上シフトされるとその値の影響は無視することができる。

以上のような考察により、 i = 4,5,6の場合の R_iを最初に独立に取り出し、 i = 1,2,3に関しては後から同様に取り出せばよいことがわかる。一度の質問で三つ全て取り出すためには、うまい数を見つけなければならないが、適当に実験を回して見つければよい。

#include <bits/stdc++.h>
using namespace std;
#define rep(i,N) for(int i=0;i<int(N);++i)
#define rep1(i,N) for(int i=1;i<int(N);++i)
typedef long long ll;
int t, w;

ll R[7];
ll V;
int main() {
    cin.tie(0);
    ios::sync_with_stdio(false);

    cin >> t >> w;
    while(t--){
        //calc
        // V[1]を使う
        cout << 191 << endl;
        cin >> V;
        R[6] = (V >> (191 / 6)) % 128;
        R[5] = (V >> (191 / 5)) % 128;
        R[4] = (V >> (191 / 4)) % 128;
        // V[2]を使う
        cout << 48 << endl;
        cin >> V;
        V -= (R[4]<<(48/4));
        V -= (R[5]<<(48/5));
        V -= (R[6]<<(48/6));
        R[3] = (V >> (48 / 3)) % 128;
        R[2] = (V >> (48 / 2)) % 128;
        R[1] = (V >> (48 / 1)) % 128;
        //output
        for(int i = 1; i <= 6; i++){
            cout << R[i];
            if(i == 6)cout << endl;
            else cout << " ";
        }
        //last input
        int valid;
        cin >> valid;
        if(valid == -1)return 0;
    }
}