题目大意:有n个数,其中有且只有两个数为y,其他的都为x,让你找到这两个为y的位置pos1,pos2。你可以问20个问题,每个问题为你给出一个集合,系统回馈给你他们的异或和。首先,每个集合中要不只有1个y,要不有0或2个y。我们通过问一个问题就可以知道这个集合中y的个数是奇数还是偶数。分以下四种情况:
1、集合大小为偶数,y有偶数个,则x有偶数个,那么返回值为0
2、集合大小为偶数,y有奇数个,则x有奇数个,那么返回值为x^y
3、集合大小为奇数,y有偶数个,则x有奇数个,那么返回值为x
4、集合大小为奇数,y有奇数个,则x有偶数个,那么返回值为y
因为x!=y,x!=0,y!=0,所以这四种返回值一定是不同的数。我们根据返回值就可以判断y的个数。
我们把每个数当做二进制,则最多有
⌊log2n⌋+1位。
我们按每一位上是1还是0分组。每次把所有数分成两组。总共分
⌊log2n⌋+1
次。每次我们把这位(0..
⌊log2n⌋
)上是1的放到a里,询问a这个子集中y的个数,如果有一个,那么显然pos1,pos2的这一位是不同的。把这种不同的位记下来,记作differ。最后找一个最小的只含1个y的子集,记这个子集的大小为m,显然m<=n/2,在这个子集里二分查找这个y的位置,需要询问
⌈log2m⌉
次,记作pos,那么另一个位置就是pos^differ。最多问19次。
#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int n,x,y,differ=0,differbit=-1;
vector<int> a;
vector<int> b;
int ask(){
if(a.empty()) return 0;
printf("? %d",a.size());
for(int i=0;i<a.size();++i)
printf(" %d",a[i]);
puts("");fflush(stdout);
int res;
scanf("%d",&res);
return res;
}
int solve(){//b这个子集中只有一个特殊值,用二分找到它。
int l=0,r=b.size()-1;
while(l<r){
int mid=(l+r)>>1;a.clear();
for(int i=l;i<=mid;++i) a.push_back(b[i]);
int res=ask();
if(res==y||res==(x^y)) r=mid;
else l=mid+1;
}return b[l];
}
int main(){
scanf("%d%d%d",&n,&x,&y);
for(int i=0;(1<<i)<=n;++i){
a.clear();
for(int j=1;j<=n;++j)
if(j&(1<<i)) a.push_back(j);
int res=ask();
if(res==y||res==(x^y)) differ|=(1<<i),differbit=i;
}
a.clear();
for(int i=1;i<=n;++i){
if(i&(1<<differbit)) a.push_back(i);
else b.push_back(i);
}
if(a.size()<b.size()) swap(a,b);
int pos1=solve();
int pos2=pos1^differ;
if(pos1>pos2) swap(pos1,pos2);
printf("! %d %d\n",pos1,pos2);
return 0;
}