题目大意
有n个玩家,n是3的倍数,有k个冒名顶替者,保证\(\frac{n}{3} < k < \frac{2n}{3}\),每次可以询问3个玩家中冒名顶替者的数量是否大于一半,最多询问n+6次,问冒名顶替者的数量和编号。
解题思路
3个一组的询问,因为k的数量限制,必定至少有一组0和一组1,然后我们从两组中各挑两个人,进行四次询问即可找出一个冒名顶替者,一个船员,具体看代码注释。一共消耗\(\frac{n}{3} + 4\)次询问。
把之前3个一组的询问结果保存下来,如果结果是1,就拿冒名顶替者问12,23两组即可确定3人身份,结果是0类似,具体看代码。一共消耗\(\frac{2n}{3}\)次询问。
代码
const int maxn = 2e5+10;
const int maxm = 2e6+10;
struct TRP {
int a[3];
};
TRP t0, t1;
int ask(int a, int b, int c) {
printf("? %d %d %d\n", a, b, c);
//cout << endl;
fflush(stdout);
int x; scanf("%d", &x);
return x;
}
vector<TRP> res[2];
int main() {
int __; cin >> __;
while(__--) {
int n; scanf("%d", &n);
res[0].clear(), res[1].clear();
for (int i = 1; i<=n; i+=3) {
int x = ask(i, i+1, i+2);
if (x) t1 = {i, i+1, i+2};
else t0 = {i, i+1, i+2};
res[x].push_back({i, i+1, i+2});
}
int f0, f1, res1, res2, cnt = 0;
cnt += (res1=ask(t0.a[0], t1.a[0], t1.a[1]));
cnt += (res2=ask(t0.a[1], t1.a[0], t1.a[1]));
/*
00 11 - 1 1 0 0
00 10 - 0 0 0 0
01 11 - 1 1 1 1
01 10 - 0 1 1 0
*/
cnt += ask(t1.a[0], t0.a[0], t0.a[1]);
cnt += ask(t1.a[1], t0.a[0], t0.a[1]);
if (cnt==0) f0 = t0.a[0], f1 = t1.a[2];
else if (cnt==4) f0 = t0.a[2], f1 = t1.a[0];
else {
if (res1+res2==2) f0 = t0.a[0], f1 = t1.a[0];
else f0 = t0.a[2], f1 = t1.a[2];
}
set<int> ans;
ans.insert(f0);
for (auto v : res[0]) {
int x = ask(f1, v.a[0], v.a[1]);
int y = ask(f1, v.a[1], v.a[2]);
if (x+y==0) {
ans.insert(v.a[0]);
ans.insert(v.a[1]);
ans.insert(v.a[2]);
}
else if (x+y==2) {
ans.insert(v.a[0]);
ans.insert(v.a[2]);
}
else if (x) {
ans.insert(v.a[1]);
ans.insert(v.a[2]);
}
else {
ans.insert(v.a[0]);
ans.insert(v.a[1]);
}
}
for (auto v : res[1]) {
int x = ask(f0, v.a[0], v.a[1]);
int y = ask(f0, v.a[1], v.a[2]);
if (x+y==2) continue;
if (x==1) ans.insert(v.a[2]);
else if (y==1) ans.insert(v.a[0]);
else ans.insert(v.a[1]);
}
printf("! %d", (int)ans.size());
for (auto v : ans) printf(" %d", v);
putchar('\n');
fflush(stdout);
}
return 0;
}