题解:CF1526F Median Queries

· · 题解

爆标。

n^3

首先你可以 n^3 的求出每一对的答案,如果 ask(a,b,c) = 1 那么 p_a,p_b,p_c 在值域上是相邻的,因为序列是较长的,所以一定有解,并且保证 p_1 < p_2 ,所以解唯一(因为将 p_i \gets n-p_i+1 之后查询结果都不变,若不保证这一点就会有两个解)。

这个 n^3 做法能告诉我们这题是可做的。

n^2

考虑对于一个点 {pos} 求出其他点的相对位置。

先考虑求 |p_{pos} - p_i| ,钦定 p_{pos}<p_{i}

定理一:发现一个 ask(a,b,c) 在数轴上的意义是按顺序排列的三点的相邻两点的距离较大值。

定理二:对于每个 p_{pos}<p_{j}<p_{i} j ask(i,j,pos) 一定小于不在区间里的 j' ask(i,j',pos)

定理二证明:

根据定理一:

--- 我们称 $ [p_{pos},p_i] $ 为区间。 然后我们发现对于在区间里的 $ j' $ 的 $ ask(i,j,pos) $ 的最小值就是区间长的一半! 因为他是 $ [p_{pos},p_j] , [p_j,p_i] $ 长度的较大值,当 $ p_j $ 在 $ [p_{pos},p_i] $ 中点时是最小的。 而我们还关心区间长的奇偶性,我们只需要数最小值出现几次即可! 我们 $ n $ 次询问求出区间长,这就是 $ p_i $ 到 $ p_{pos} $ 的距离!随便钦定一点在 $ p_{pos} $ 左边,通过定理二判断其他 $ p_i $ 在左边还是在右边,最后判断 $ p_1,p_2 $ 的大小关系镜像处理即可。 ## $ 2n + C

我们抛开求每个点到一个点的距离的想法,考虑求极值。

具体来说如果我们找到了 p_i=1,p_j=2 ,那么 p_k=ask(i,j,k)+2 ,我们就可以用 n-2 次询问求出答案!

现在我们需要用 C+n+2 次询问找到这个极值。

假设我们有一个较短的区间 [p_l,p_r] ,由于序列是较长的,根据定理一,我们可以找到所有 ask(i,l,r) 最大次大 i 并认为他们是极值,因为他们是在数轴上距离 [p_l,p_r] 最远的点,自然是极值。

而且我们能发现较短的区间的定义是 len \leq \frac{n}{3} ,因为若区间长 > \frac{n}{3} ,那么可能存在两端的极值 ask(l,r,i) = | p_r-p_l | ,这样你就无法区分极值了。

所以如果我们找到一个较短的区间,用 n-2 次询问就可以找到极值!

考虑用 C+4 次询问找到任意较短的区间,一个很好的想法就是随机化,每次随一个 a,b,c ,若 ask(a,b,c) \leq \frac{n}{6} ,那么这三点中任意两点构成的区间都是较短的!

证明:

p_a<p_b<p_c .

那么根据定理一有 \frac{n}{6} \geq ask(a,b,c) = \max(p_b-p_a,p_c-p_b) \geq \frac{p_c-p_a}{2} .

所以 p_c-p_a \leq \frac{n}{3} .

--- 随一次随不到的概率约为 $ \frac{8}{9} $ ,那么随 $ C+4 $ 次随不到的概率是 $ {(\frac{8}{9})}^{C+4} $ ,是极小的,所以可以认为这是正确的。 ## $ 2n

我们再抛开找极值的思路。

考虑将找极值弱化,只要找到一对相邻的元素,即长度为 1 的区间。

怎么找呢?根据 n^2 做法,考虑 [p_1,p_2] 这个区间,先用 n-2 次询问算出这个区间长度 len ,根据定理二能知道那些点在区间里面,即满足 p_1 < p_i < p_2

能把区间内的每一个元素求出来吗?考虑随便找到一个 {mid} (区间长为奇数的时候不关心这是靠左的中点还是靠右的中点),用 len 次询问同理得出 p_2-p_{mid} ,再次根据定理二得知那些点在区间 [p_{mid},p_2] 里!由于这是中点,所以这样你就可以知道区间里每一个 ask(1,2,i) p_2-p_i 还是 p_i-p_1

我们用 len+n-2 次询问,但是求出了区间内的所有点,我们在区间里找到 pos 满足 p_{pos}=p_1+1 ,那么我们就找到了一对相邻的元素,满足 ask(1,pos,i) = p_1-p_i p_i-p_{pos} ,这是显然的。

我们对于不在区间里的 n-len-2 个点询问 ask(1,pos,i) ,如果我们知道这个点是 p_i<p_1 还是 p_{pos}<p_i 我们就做完了!

我们结合 ask(1,2,i) 进行分类讨论:

1.若 ask(1,2,i)=len,ask(1,pos,i)<len ,那么 p_i<p_1

2.若 ask(1,2,i)=len,ask(1,pos,i)>len ,那么 p_i>p_{pos}

3.若 ask(1,2,i)>len,ask(1,pos,i)=ask(1,2,i) ,那么 p_i<p_1

4.若 ask(1,2,i)>len,ask(1,pos,i) \ne ask(1,2,i) ,那么 p_i>p_{pos}

5.若 ask(1,2,i)=len,ask(1,pos,i)=len ,此时你无法只通过这两种询问区分 p_i=p_1-len p_i=p_{pos}+len 。因为这种情况只会出现一次,我们可以询问 ask(1,mid,i) 来区分他们(总之是好做的)。

这部分读者根据定理一手玩即可发现。

总询问次数为 $ n+len-2+n-len-2+1=2n-3 $。 但是我们刚刚都是默认区间较长,应为如果区间较短可能出现包括但不限于无法区分 $ len=1 $ 和 $ len=2 $ 的区间、$ mid=pos $ 等等问题,尤其是对于 $ p_1=1,p_2=2 $ 的情况是很棘手的。 所以我们要接着大分讨? 不妨回头看看,对于**较短**的区间我们不是已经有做法了吗! 于是我们的总操作次数为 $ \max(2n-3,2n-2)=2n-2 $。 代码: ```cpp #include <bits/stdc++.h> using namespace std; typedef long long LL; const int N=1e5+10; void over(){cout<<"dou guai sqy"<<endl;exit(0);} int n,l,r,len,d1[N],d2[N],ans[N]; int ask(int a,int b,int c){ int op=0; cout<<"? "<<a<<' '<<b<<' '<<c<<endl; cin>>op; if(op==-1) over(); return op; } namespace sol1{ void work(){ int fl=0,fr=0,m=0,c=0; for(int i=1;i<=n;i++) m=max(m,d1[i]); for(int i=1;i<=n;i++) if(d1[i]==m) fr=i; for(int i=1;i<=n;i++) if(d1[i]==m-1){ if(fl==0) fl=i; else{ if(ask(fr,l,i)<ask(fr,l,fl)) fl=i; } } ans[fr]=1; ans[fl]=2; for(int i=1;i<=n;i++) if(i!=fr && i!=fl) ans[i]=ask(fl,fr,i)+2; } } namespace sol2{ int chk(int Minid,int m){ int Min=len,c=0; for(int i=1;i<=n;i++) if(d1[i]<len && i!=l && i!=r && i!=Minid){ Min=min(Min,d2[i]); } for(int i=1;i<=n;i++) if(d1[i]<len && i!=l && i!=r && i!=Minid){ if(d2[i]==Min) c++; } return Min*2+1-c; } void work(int Minid){ int m=d1[Minid],c=0; for(int i=1;i<=n;i++) if(d1[i]==m) c++; len=m*2+1-c; ans[l]=1; ans[r]=len+1; int fl=0,id=0; for(int i=1;i<=n;i++) if(d1[i]<len && i!=l && i!=r && i!=Minid){ d2[i]=ask(Minid,i,r); } int llen=chk(Minid,m); ans[Minid]=ans[r]-llen; for(int i=1;i<=n;i++) if(d1[i]<len && i!=l && i!=r && i!=Minid){ if(d2[i]<llen) ans[i]=ans[l]+d1[i]; else ans[i]=ans[r]-d1[i]; } for(int i=1;i<=n;i++) if(ans[i]==m+1) id=i; for(int i=1;i<=n;i++) if(d1[i]<len && ans[i]==2) fl=i; for(int i=1;i<=n;i++) if(d1[i]>=len){ int k=ask(l,fl,i); if(d1[i]==len){ if(k==len){ if(ask(l,id,i)==len) ans[i]=ans[l]-k; else ans[i]=ans[fl]+k; } else if(k<len) ans[i]=ans[l]-k; else ans[i]=ans[fl]+k; }else{ if(d1[i]==k) ans[i]=ans[l]-k; else ans[i]=ans[fl]+k; } } } } void work(){ cin>>n; l=1,r=2; for(int i=3;i<=n;i++) d1[i]=ask(l,r,i); int Minid=3; for(int i=4;i<=n;i++) if(d1[i]<d1[Minid]) Minid=i; if(d1[Minid]<=2){ sol1::work(); }else{ sol2::work(Minid); } } int tid[N]; bool cmp(int i,int j){return ans[i]<ans[j];} void out(){ for(int i=1;i<=n;i++) tid[i]=i; sort(tid+1,tid+1+n,cmp); for(int i=1;i<=n;i++) ans[tid[i]]=i; if(ans[1]>ans[2]){ for(int i=1;i<=n;i++) ans[i]=n-ans[i]+1; } cout<<"! "; for(int i=1;i<=n;i++) cout<<ans[i]<<' '; cout<<endl; int op=0; cin>>op; if(op==-1) over(); } void clear(){ l=r=len=0; for(int i=1;i<=n;i++){ d1[i]=d2[i]=ans[i]=0; } } int main(){ int T=1; cin>>T; for(int c=1;c<=T;c++){ work(); out(); clear(); } cerr<<1.0*clock()/CLOCKS_PER_SEC<<'\n'; return 0; } //nzq ```