CF1451E2 Bitwise Queries (Hard Version) 题解
看着这到题的题解我感觉都写的很诡异,所以我决定贡献一发题解。
题目大意:有一个长度为 \(n\) 的数组( \(n\) 是 \(2\) 的幂),有 \(3\) 种操作,AND OR XOR,可以获得数组两个元素的 AND OR XOR 值,仅限 \(n+1\) 次操作求原数组。
首先考虑以 \(a_1\) 为基准求出 \(a_1\oplus a_k\),这样只要求出 \(a_1\) 就可以求出整个数组,这一步需要 \(n-1\) 次操作。
因为只有 \(n\) 个元素,元素的范围是 \([0,n-1]\),所以可以进行分类讨论:
1.所有元素并不是互不相同。也就是说有重复的元素,这样只要找到相同的元素,对相同的元素进行 AND 操作就可以求出这个元素(因为知道了这两个元素和 \(a_1\) 的异或值),又因为知道了所有元素和 \(a_1\) 的 XOR,所以可以直接算出整个数组。考虑如何找到相同的元素,可以开个桶记录每个元素出现的次数。取最大的出现次数(这种情况下至少两次),然后进行一次查找就可以得到所有异或 \(a_1\) 相同的元素也就是相同的元素,任意取两个询问 AND 就可以了。不过这里有个要注意的,如果 \(a_1\) 恰好是重复的元素只会扫到 \(1\) 个不一样的元素,取 \(1\) 询问即可。
2.所有元素互不相同。也就是是 \([0,n-1]\) 的全排列。考虑 XOR 的性质,相同就是 \(0\),也就是说可以选择 \(1\) 这个数,只有最后一位是 \(1\),其他都是 \(0\),也就是说如果 \(a_1\oplus a_k=1\),\(a_1\) 和 \(a_k\) 只有最后一位不一样,可以通过 AND 操作求出前 \(\log n-1\) 位。同理如果 \(a_1\oplus a_k=\frac n2\),\(a_1\) 和 \(a_k\) 只有第一位不一样,可以通过 AND 操作求出后 \(\log n-1\) 位。把两个结果组合一下(直接或起来)就可以得到 \(a_1\)。其实不一定要选择 \(1\) 和 \(\frac n2\),只要保证这两个数二进制下每位都至少有个数是 \(0\) 就行。
code:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
constexpr int kN=(1<<16)+7;
int xr[kN],n,cnt[kN];
using std::cin,std::cout,std::endl;
auto AND=[](int a,int b){cout<<"AND "<<(a)<<' '<<(b)<<endl;int t;cin>>t;return t;};
auto XOR=[](int a,int b){cout<<"XOR "<<(a)<<' '<<(b)<<endl;int t;cin>>t;return t;};
signed main(){
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
std::ios::sync_with_stdio(false);
cin.tie(nullptr);
cin>>n;cnt[0]=1;
for(int i=2;i<=n;i++)cnt[xr[i]=XOR(1,i)]++;
if(int p=std::max_element(cnt,cnt+n)-cnt;cnt[p]!=1){// 有重复
std::basic_string<int>g;
for(int i=2;i<=n;i++)if(xr[i]==p)g+=i;
int ak=AND(g.size()==1?1:g.front(),g.back()),a1=ak^p;
cout<<"! "<<a1;
for(int i=2;i<=n;i++)cout<<' '<<(a1^xr[i]);
}else{// 没重复
int xp=-1,yp=-1,tx=1,ty=n>>1;
for(int i=2;i<=n;i++){
if(xr[i]==tx)xp=i;
if(xr[i]==ty)yp=i;
}
int a1=AND(1,xp)|AND(1,yp);
cout<<"! "<<a1;
for(int i=2;i<=n;i++)cout<<' '<<(a1^xr[i]);
}
cout.flush();
return 0;
}