题解:P9721 [EC Final 2022] Inversion
新教练布置的题,挺有意思的。我的做法不太用预处理很多,常数挺小的,没人写过就写个题解。
首先考虑
很容易想到从第一小开始依次比较,但是至少
问题来了,怎么通过题设交互方法实现任意位置
- 两数在序列中相邻:
? i j返回的就是i>j是真是假。 - 两数不相邻:我有一个不一样的思路:
首先询问
? i j和? i+1 j记结果为q1,q2 显然
q1 即为(q2 + \sum\limits^{j}_{k=i+1} [p_i>p_k] )\mod 2 = (q2 + \sum\limits^{j-1}_{k=i+1} [p_i>p_k] + [p_i>p_j])\mod 2 意思是
i\sim j 的逆序对个数,等于i+1 \sim j 的逆序对个数,再加上i+1 \sim j 中比第i 项小的个数我们可以维护一个
sum[i],表示第i 项到当前循环处理到的最后一项之间,有几个是小于第i 项的。维护就是:每加入一个数,只用把比他大的所有数的sum[i]增加1 即可。所以只需判断
(q2 + sum[i]) % 2与q1的奇偶性是否发生了变化即可判断[p_i > p_j] 是否为真。
至此,题目迎刃而解,感觉思路还是挺自然的,不会很复杂,实现也挺简单,很小清新的题。
#include<bits/stdc++.h>
using namespace std;
const int _=2005;
int n;
int sum[_],rk[_],id[_];
int main(){
cin>>n;
rk[1]=1;
for(int i=2;i<=n;++i){
int l=1,r=i-1,ans=0;
while(l<=r){
int mid=(l+r)>>1;
int q1,q2;
cout<<"? "<<rk[mid]<<" "<<i<<endl;
cin>>q1;
if(rk[mid]==i-1){
if(q1) r=mid-1;
else ans=mid,l=mid+1;
}
else{
cout<<"? "<<rk[mid]+1<<" "<<i<<endl;
cin>>q2;
if((q2+sum[rk[mid]])%2==q1) ans=mid,l=mid+1;
else r=mid-1;
}
}
for(int j=i;j>ans+1;--j) rk[j]=rk[j-1];
rk[ans+1]=i;
for(int j=ans+2;j<=i;++j) ++sum[rk[j]];
}
for(int i=1;i<=n;++i) id[rk[i]]=i;
cout<<"! ";
for(int i=1;i<=n;++i) cout<<id[i]<<" ";
cout<<endl;
return 0;
}