题解:CF1526F Median Queries
BPG_ning
·
·
题解
爆标。
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
```