CF1553E Permutation Shift 题解
Post time: 2021-07-24 13:05:12
一道心理阴影题。
题意已经很明显了,\(O(n^2)\) 做法就是,对于每个 \(k\),求最少多少次交换可以使一个排列变为另一个排列。做法是说,对于同一个数,若它在两个排列中的位置不同(记这样的数的总数为 \(s\)),就建一条连接这两个位置的边,求得图的连通分量数 \(c\),答案就是 \(s-c\)。
考场上想到这个,但是无论如何都不会优化了。不会优化就直接做啊!
考虑到这个题给了一个限制 \(m\),最理想的情况下,可以做有 \(2m\) 个数位置不对的两个排列。那么 \(s>2m\) 的肯定都不可以。题目中给了一个 \(m\leq \frac{n}{3}\),这时候就派上用场了:设旋转参数为 \(k\) 的时候,两个排列位置一样的个数为 \(cnt_k\),那么 \(\sum_{i=0}^{n-1}cnt_i=n\)。所以,满足 \(s\leq 2m\) 的 \(k\) 的数量最多不超过 \(3\)!直接 \(O(3n)\) 做完了。
点击查看代码
#include<iostream>
#include<cstdio>
#include<vector>
#define pb push_back
using namespace std;
const int N=3e5+13;
int n,m,cnt[N],p[N],ans[N];
vector<int> g[N];
bool vis[N];
inline void add_edge(int u,int v){g[u].pb(v);}
void dfs(int u){
vis[u]=1;
for(auto v:g[u])
if(!vis[v]) dfs(v);
}
inline void solve(){
scanf("%d%d",&n,&m);
for(int i=0;i<n;++i) cnt[i]=0;
for(int i=1;i<=n;++i){
int x;scanf("%d",&x);
p[x]=i;
cnt[(i-x+n)%n]++;//这种情况下,x这个数的位置不需要动
}
int t=0;
for(int k=0;k<n;++k){
if(cnt[k]<n-2*m) continue;//不可能成功了,直接特判掉,能往下走的k最多3个
for(int i=1;i<=n;++i) vis[i]=0,g[i].clear();
for(int i=1;i<=n;++i){
int pos=(i+k-1)%n+1;
if(pos!=p[i]) add_edge(pos,p[i]);//加边
}
int c=0;
for(int i=1;i<=n;++i){
int pos=(i+k-1)%n+1;
if(pos!=p[i]&&!vis[pos]) dfs(pos),++c;//求连通分量个数
}
if(n-cnt[k]-c<=m) ans[++t]=k;//最后判断是否可行
}
printf("%d ",t);for(int i=1;i<=t;++i) printf("%d ",ans[i]);putchar('\n');
}
int main(){
int T;scanf("%d",&T);
while(T--) solve();
return 0;
}
//E