题解【CF1937C. Bitwise Operation Wizard】
CF1937C. Bitwise Operation Wizard
好玩的交互。
题意:
给定 \(0,1,\dots,n-1\) 的一个排列 每次可以选取四个数 \(0 \leq a,b,c,d \leq n-1\),查询 \((p_a~|~p_b)\) 与 \((p_c~|~p_d)\) 之间的大小关系。要求在 \(3n\) 次查询内找到两个数 \(i,j\),使得 \(p_i \oplus p_j\) 最大。
首先考虑如何使两个数的异或最大。根据位运算的性质,我们不难得出:
在 \(i,j\) 的第 \(1 \sim k\) 位都不相同时取到该最大值。
在 \(i,j\) 的第 \(1 \sim k\) 位都不为 \(0\) 时取到该最大值。可以推出 \((1)\) 式取到最大值时 \((2)\) 式也取到最大值。
因为我们必然要使答案的第 \(k\) 位为 \(1\),那么我们要先找到一个第 \(k\) 位为 \(1\) 的数。因为我们能够查询大小关系,所以我们考虑找到排列中最大的一个数即 \(n-1\) 的位置。
由 \(a~|~a=a\) 可知,比较 \((p_a~|~p_a)\) 与 \((p_b~|~p_b)\) 的大小关系等价于查询 \(p_a\) 与 \(p_b\) 的大小关系。所以我们可以很容易的通过这样 \(n-1\) 次比较找到最大值的位置。
接下来考虑找到数 \(t=(2^k-1)\oplus(n-1)\) 的位置。容易知道 \(t\oplus(n-1)=t~|~(n-1)=2^k-1\)。
则 \(t\) 与 \(n-1\) 满足 \((1)(2)\) 两式的最大值条件。而在满足 \(x~|~(n-1)=2^k-1\) 的 \(x\) 中,\(t\) 一定是最小的那个数(因为在 \(n-1\) 取 \(1\) 的位上 \(t\) 一定取 \(0\))。
所以我们考虑取出使得 \((n-1)|p_i\) 取最大值的所有位置,然后在这些位置中找到最小的数的位置,这个位置的数就是 \(t\)。可以知道比较的次数不会超过 \(2(n-2)\)。
最后我们就在 \(3n\) 步内得到了 \(n-1\) 与 \(t\) 的位置,输出即可。
以上针对 \(n \geq 4\),注意 \(n=2\) 与 \(n=3\) 的边界情形。
inline char query(int p1_1,int p1_2, int p2_1,int p2_2)
{
cout<<"? "<<p1_1-1<<' '<<p1_2-1<<' '<<p2_1-1<<' '<<p2_2-1<<endl;
char op; cin>>op;
return op;
}
inline int ans_out(int p1,int p2)
{
cout<<"! "<<p1-1<<' '<<p2-1<<endl;
return 0;
}
inline int Solve()
{
#ifdef DEBUG
printf("Debuging...\n");
#endif
cout.flush();
cin>>n;
char op;
if(n==2) return ans_out(1,2);
if(n==3)
{
op=query(1,2, 1,3);
int k=(op=='>')?2:3;
op=query(1,k, k,k^1);
int l=(op=='>')?1:k^1;
return ans_out(k,l);
}
int k=1;
for(int i=2;i<=n;i++)
{
op=query(k,k, i,i);
if(op=='<') k=i;
}
vector<int> com;
int l=(k==1)?2:1;
com.push_back(l);
for(int i=1;i<=n;i++)
{
if(i==k || i==l) continue;
op=query(k,l, k,i);
if(op=='<')
{
vector<int>().swap(com);
l=i;
com.push_back(l);
}
else if(op=='=')
com.push_back(i);
}
l=com[0];
for(int i=1;i<com.size();i++)
{
op=query(l,l, com[i],com[i]);
if(op=='>') l=com[i];
}
ans_out(k,l);
return 0;
}