猜01序列
猜01序列
01交互,随机化
题目
给定正整数 \(n\),评测机生成一个长度为 \(n\) 的 01 序列 \(a_1, a_2, \dots, a_n\),保证至少有一个 \(1\)。
每次询问可以选一个下标集合 \(I\),评测机返回:
最多询问 \(17\) 次,求 \(\sum_{i=1}^{n} a_i\)。
数据范围:\(1 \le n \le 10^5\)。
核心观察
设整个序列中 \(1\) 的总数为 \(s\)。对某次询问,若集合 \(I\) 中有 \(x\) 个 \(1\),则补集中有 \(s - x\) 个 \(1\),评测机返回:
如果我们能知道 \(x\),就可以解出 \(s\)。但问题是 \(x\) 未知。
不过反过来想:对于一个正确的 \(s\),方程 \(x(s-x) = r\) 必须有整数解。这个二次方程的判别式为:
所以对正确的 \(s\),\(s^2 - 4r\) 必须是完全平方数。
关键构造:二进制编号
因为 \(n \le 10^5 < 2^{17}\),所以可以给每个位置分配一个唯一的 \(17\) 位二进制编号。
具体做法:
- 生成 \(0, 1, 2, \dots, n-1\) 的随机排列 \(\text{perm}\);
- 位置 \(i\)(从 \(1\) 开始)的编号为 \(\text{perm}[i-1]\);
- 对第 \(j\) 位(\(j = 0, 1, \dots, 16\)),询问所有编号第 \(j\) 位为 \(1\) 的位置。
设第 \(j\) 次询问的返回值为 \(r_j\),集合中 \(1\) 的个数为 \(x_j\),则:
因此对正确的 \(s\),对所有 \(j\) 都有:
且 \(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;
}

浙公网安备 33010602011771号