专栏文章
CF2127G2 题解
CF2127G2题解参与者 1已保存评论 0
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 0 条
- 当前快照
- 1 份
- 快照标识符
- @minnxohy
- 此快照首次捕获于
- 2025/12/02 05:30 3 个月前
- 此快照最后确认于
- 2025/12/02 05:30 3 个月前
单个询问几乎没有信息,考虑两个近似排列答案的变化量。
如果没有 的限制,那么询问 的每个循环移位答案都不变,因为原本的 入边贡献必定减少,出边贡献必定增多。
那么加上 的限制,如果我们知道无限制时的答案,那么就能知道 的出边是否属于 。
不妨取 ,那么每次询问 就能知道每个 在 中与 的距离是否 。
那么我们如果构造 个 使得对于每个 ,所有的 每次回答不完全相同。
记 ,则对于任意 存在一个 使得 (减法是 意义下的)。
-
如果 是偶数,我们可以按如下方式构造:初始 ,,如果 的第 个二进制位是 就交换 ,其中 。考虑任意的 ,我们要求存在一个 使得 的第 位相同或不同,由于 所以必定存在。
-
如果 是奇数,我们可以按如下方式构造:,其中 。对于 ,只要某个 满足 就一定能得到不同的结果,很显然。
想要知道 在无限制下的答案,需要先确定任意一个 。
如果 是偶数,设 ,则询问 和 ,此时对于任意 , 在两个排列中恰有一个产生贡献,除非 。
那么我们固定 ,枚举 就能找到 的 ,具体来说找到两次询问之和 的一个作为 即可。
如果 是奇数,那么固定 ,然后 都不满足的时候则 ,注意此时 时答案之和不一定是 ,要找和最大的一个作为 。
因此可以在 次操作内确定 ,然后 次询问还原排列,总次数 。
时间复杂度 。
代码:
CPP#include<bits/stdc++.h>
using namespace std;
int n,a[105][105],b[105],p[105],q[105],w[105];
int ask() {
cout<<"?";
for(int i=1;i<=n;++i) cout<<" "<<q[i];
cout<<endl;
int o; cin>>o; return o;
}
void solve() {
cin>>n;
int k=n/2+1,x=n,y=k; //find p[x]=y
cout<<k<<endl;
for(int i=1,m=n-(n&1),c=0;i<=m;++i) if(i!=k) {
iota(q+1,q+n+1,1),swap(q[i],q[k-1]);
int z=ask();
reverse(q+1,q+m+1),swap(q[k],q[k-1]),z+=ask();
if(i>1&&z>c) { x=i; break; }
else if(c>z) { x=1; break; }
else c=z;
}
memset(a,0,sizeof(a)),memset(b,0,sizeof(b));
iota(p+1,p+n+1,1);
for(int d=0;d<7;++d) {
if(~n&1) {
iota(p+1,p+n+1,1);
for(int i=1;i<=n/2;++i) if((i-1)>>d&1) swap(p[i],p[i+n/2]);
}
for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) a[i][j]|=((p[j]+n-p[i])%n<=n-k)<<d;
for(int i=1;i<=n;++i) q[p[i]]=i;
for(int i=1;i<=n;++i) w[q[k]]=ask(),rotate(q+1,q+2,q+n+1);
int o=w[x]+((p[y]+n-p[x])%n<=n-k);
for(int i=1;i<=n;++i) b[i]|=(o-w[i])<<d;
if(n&1) for(int i=1;i<=n;++i) p[i]=(p[i]-1)*2%n+1;
}
for(int i=1;i<=n;++i) q[i]=find(a[i]+1,a[i]+n+1,b[i])-a[i];
cout<<"!";
for(int i=1;i<=n;++i) cout<<" "<<q[i];
cout<<endl;
}
signed main() {
int _; cin>>_;
while(_--) solve();
return 0;
}
相关推荐
评论
共 0 条评论,欢迎与作者交流。
正在加载评论...