鱼和熊掌不可兼得
题目描述
交互。每次可以询问一个排列,返回这个排列与答案排列相同位置的个数,求出这个答案排列。
数据范围
$n \le 5 \times 10^3;queries \le 5 \times 10^4$
题解
考虑先求出一个错排,那答案会形如若干个置换环的形式。所以我们的目标是在 $\frac{(n-1)\times n}{2}$ 条边中,选出 $n$ 条有效边。
考虑有效边的性质,即交换这个有效边的端点数值,询问结果会增加。所以对于每条边 $(i,j)$ ,考虑按照 $(i+j)\%n$ 分组,每组每条边的两个端点就互不相同,那我们可以用类似分治的方法求出哪些边是有效的,最后定向即可。
代码
#include "game.h" using namespace std; const int N=5005; struct O{int x,y;}; vector<O>p[N],h; vector<int>f[N],a; int g[N<<2],S[N],cnt,lst,d[N]; bool vis[N]; bool J(int u){return d[u]<2;} void add(int u,int v){d[u]++;f[u].push_back(v);} void find(int k,int i,int l,int r){ if (l==r){ add(p[i][l].x,p[i][l].y); add(p[i][l].y,p[i][l].x); return; } int Ls=(k<<1),Rs=(Ls|1),mid=(l+r)>>1; for (int j=l;j<=mid;j++) swap(a[p[i][j].x],a[p[i][j].y]); g[Ls]=count(a);g[Rs]=g[k]-g[Ls]; for (int j=l;j<=mid;j++) swap(a[p[i][j].x],a[p[i][j].y]); if (g[Ls]) find(Ls,i,l,mid); if (g[Rs]) find(Rs,i,mid+1,r); } void dfs(int u){ vis[u]=1;S[++cnt]=u; int z=f[u].size(); for (int i=0;i<z;i++) if (!vis[f[u][i]]){ swap(a[u],a[f[u][i]]); dfs(f[u][i]); } } vector<int>guess(int n,int limit){ srand(233);a.resize(n); for (int i=1;i<=n;i++) a[i-1]=i; if (n==1) return a; while(count(a)) random_shuffle(a.begin(),a.end()); for (int i=0;i<n;i++) for (int j=i+1;j<n;j++) p[(i+j)%n].push_back((O){i,j}); for (int z,i=0;i<n;i++){ z=p[i].size();if (!z) continue;h.clear(); for (int j=0;j<z;j++) if (J(p[i][j].x) && J(p[i][j].y)) h.push_back(p[i][j]); p[i]=h;z=p[i].size();if (!z) continue; for (int j=0;j<z;j++) swap(a[p[i][j].x],a[p[i][j].y]); g[1]=count(a); for (int j=0;j<z;j++) swap(a[p[i][j].x],a[p[i][j].y]); if (g[1]) find(1,i,0,z-1); } for (int i=0,v;i<n;i++) if (!vis[i]){ cnt=0;dfs(i);v=count(a); if (v==lst){ for (int j=cnt-1;j;j--) swap(a[S[j+1]],a[S[j]]); swap(a[S[1]],a[S[cnt]]); for (int j=cnt-1;j>1;j--) swap(a[S[j+1]],a[S[j]]); } lst+=cnt; } return a; }