P5208-[WC2019] I 君的商店【交互,二分】
正题
题目链接:https://www.luogu.com.cn/problem/P5208
题目大意
有一个长度为\(n\)的\(01\)序列\(a\),你知道里面有奇数个\(1\)还是偶数个\(1\)。你每次可以选择两个下标集合\(S/T\)询问集合\(S\)和集合\(T\)位置的数字和哪个更大。
交互库只会告诉你\(S\leq T\)或者\(S\geq T\)。
要求在所有询问集合大小之和不超过 \(500100\) 的情况下得到整个\(a\)序列。
\(1\leq n\leq 10^5\)
解题思路
注意到数据中有一个点保证了\(a\)序列单调,我们先假设为单调不升。
此时有\(a_1=1\),那么我们考虑二分,对于一个位置\(mid\),我们判是否\(a_{mid}+a_{mid+1}\leq 1\),如果是,那么说明\(a_{mid+1}=0\),否则\(a_{mid}+a_{mid+1}\geq 1\)就说明了\(a_{mid}=1\)。
这样我们同样每次能过缩小一半,但是最后会剩下两个数字,我们用奇偶性去判断。
那么对于一般的情况,我们考虑也去构造一个单调不升的序列,两个数字比不出东西,我们考虑拿三个数字去比较,假设我们现在有一个\(a\leq b\)。
我们拿\(a+b\)和\(c\)去比较:
- 若\(a+b\leq c\),那么有\(a=0\)。
- 若\(a+b\geq c\),那么有\(b\geq c\),我们把\(b\)加入队列,这样加入队列的都是单调不升的。
code
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1e5+10;
int p[N],A[2],B[2];
int query(int *S,int nS,int *T,int nT);
void find_price(int task_id, int n, int k, int ans[]) {
if(n==1){ans[0]=1;return;}
if(task_id==3||n==2){
A[0]=0;B[0]=n-1;
if(!query(A,1,B,1))
for(int i=1;i<=n;i++)p[i]=i-1;
else
for(int i=1;i<=n;i++)p[i]=n-i;
int l=1,r=n-1;
while(l<r){
int mid=(l+r+1)>>1;
A[0]=p[mid];A[1]=p[mid+1];B[0]=p[1];
if(query(A,2,B,1))
r=mid-1;
else l=mid;
}
ans[p[l+1]]=(l&1)^k;
for(int i=1;i<=l;i++)ans[p[i]]=1;
}
else{
int x=0,y=1,tot=0;
for(int i=2;i<n;i++){
int z=i;A[0]=y;B[0]=z;//y>=z
if(query(A,1,B,1))swap(y,z);
A[0]=x;B[0]=y;B[1]=z;
if(query(A,1,B,2)){
p[++tot]=x;
x=y;y=z;
}
else ans[z]=0;
}
A[0]=x;B[0]=y;
if(query(A,1,B,1))p[++tot]=y;
else p[++tot]=x,swap(x,y);
for(int i=1;i<tot-i+1;i++)swap(p[i],p[tot-i+1]);
int l=1,r=tot-1;
while(l<r){
int mid=(l+r+1)>>1;
A[0]=p[mid];A[1]=p[mid+1];B[0]=p[1];
if(query(A,2,B,1))
r=mid-1;
else l=mid;
}
for(int i=1;i<=l;i++)ans[p[i]]=1;
y=p[l+1];A[0]=x;B[0]=y;//x>=y
if(query(A,1,B,1))swap(x,y);
A[0]=x;A[1]=y;B[0]=p[1];
if(query(A,2,B,1))ans[y]=0;
else ans[x]=1,l++,x=y;
ans[x]=(l&1)^k;
}
return;
}