猜01序列

猜01序列

01交互,随机化

题目

给定正整数 \(n\),评测机生成一个长度为 \(n\) 的 01 序列 \(a_1, a_2, \dots, a_n\),保证至少有一个 \(1\)。

每次询问可以选一个下标集合 \(I\),评测机返回:

\[\left(\sum_{j \in I} a_j\right) \cdot \left(\sum_{j \notin I} a_j\right) \]

最多询问 \(17\) 次,求 \(\sum_{i=1}^{n} a_i\)。

数据范围:\(1 \le n \le 10^5\)。


核心观察

设整个序列中 \(1\) 的总数为 \(s\)。对某次询问,若集合 \(I\) 中有 \(x\) 个 \(1\),则补集中有 \(s - x\) 个 \(1\),评测机返回:

\[r = x(s - x) \]

如果我们能知道 \(x\),就可以解出 \(s\)。但问题是 \(x\) 未知。

不过反过来想:对于一个正确的 \(s\),方程 \(x(s-x) = r\) 必须有整数解。这个二次方程的判别式为:

\[\Delta = s^2 - 4r \]

所以对正确的 \(s\),\(s^2 - 4r\) 必须是完全平方数。


关键构造:二进制编号

因为 \(n \le 10^5 < 2^{17}\),所以可以给每个位置分配一个唯一的 \(17\) 位二进制编号。

具体做法:

  1. 生成 \(0, 1, 2, \dots, n-1\) 的随机排列 \(\text{perm}\);
  2. 位置 \(i\)(从 \(1\) 开始)的编号为 \(\text{perm}[i-1]\);
  3. 对第 \(j\) 位(\(j = 0, 1, \dots, 16\)),询问所有编号第 \(j\) 位为 \(1\) 的位置。

设第 \(j\) 次询问的返回值为 \(r_j\),集合中 \(1\) 的个数为 \(x_j\),则:

\[r_j = x_j (s - x_j) \]

因此对正确的 \(s\),对所有 \(j\) 都有:

\[s^2 - 4 r_j \ge 0 \]

且 \(s^2 - 4 r_j\) 是完全平方数。


求解答案

从小到大枚举 \(s = 1, 2, \dots, n\),检查是否对所有已询问的位 \(j\) 都满足:

  • \(s^2 - 4 r_j \ge 0\);
  • \(s^2 - 4 r_j\) 是完全平方数。

第一个满足全部条件的 \(s\) 即为答案。


为什么必须随机化编号

如果直接用固定的编号(例如位置 \(i\) 的编号就是 \(i - 1\)),会出问题。

原因在于判别式 \(s^2 - 4r\) 是完全平方数这个条件,对某些 \(x\) 是"无信息"的:

\(x\) \(r = x(s-x)\) \(s^2 - 4r\)
\(0\) 或 \(s\) \(0\) \(s^2\),对任意 \(s\) 都是完全平方
\(1\) 或 \(s-1\) \(s-1\) \((s-2)^2\),对任意 \(s\) 都是完全平方

如果隐藏序列恰好让很多位落在上述情况,那么一个错误的 \(s'\) 也可能通过全部 \(17\) 个检查,于是返回错误答案。

而隐藏序列是评测机预先固定的(非自适应)。当编号固定时,位置和比特之间有确定关系,隐藏序列可能恰好构造出大量"无信息"的位。

把"位置 \(\to\) 编号"的映射随机打乱之后:

  • 隐藏序列无法针对随机的编号布局;
  • 对任意错误的 \(s' \ne s\),它在某个位恰好通过检查的概率大约是 \(O(1/s)\);
  • 要全部 \(17\) 个位都恰好通过,概率不超过 \((1/s)^{17}\),几乎为 \(0\)。

因此正确的 \(s\) 是唯一能通过全部检查的,第一个枚举到的即为答案。


询问次数

  • 最多询问 \(17\) 次(每一位一次),符合题目限制;
  • 若某一位没有位置为 \(1\),跳过该询问,对应 \(r_j = 0\);
  • 输出答案不计入询问次数。

复杂度

  • 询问次数:\(\le 17\);
  • 枚举检查:\(O(17n)\),\(n \le 10^5\);
  • 空间:\(O(n)\)。

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll MOD = 998244353;
const int BITS = 17;

void solve() {
    int n; cin >> n;
    
    vector<int> perm(n);
    iota(perm.begin(), perm.end(), 0);
    
    mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());//梅森旋转随机数生成器
    shuffle(perm.begin(), perm.end(), rng);//随机打乱

    vector<ll> val(BITS, 0);
    for(int i = 0; i < BITS; i ++) {
        vector<int> pos;
        for(int j = 0; j < n; j ++) {
            if(perm[j] & (1 << i)) pos.push_back(j + 1);
        }

        if(!pos.empty()) {
            cout << "? " << pos.size();
            for(int p : pos) cout << " " << p;
            cout << "\n"; cout.flush();

            ll r; cin >> r;
            val[i] = r;
        }
    }

    for(int s = 1; s <= n; s ++) {
        bool flag = true;
        for(int j = 0; j < BITS; j ++) {
            ll r = val[j];
            ll res = 1LL * s * s - 4 * r;
            if(res < 0) {flag = false; break;}
            ll q = sqrt(res);
            if(q * q != res) {flag = false; break;}
        }
        if(flag) {
            cout << "! " << s << "\n";
            cout.flush();
            return;
        }
    }
}

int main() {
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    int _ = 1; //cin >> _;
    while(_--) solve();
    return 0;
}
posted @ 2026-11-03 21:58  thenrify  阅读(2)  评论(0)    收藏  举报