cunzai_zsy0531

关注我

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
posted @ 2022-04-21 21:57  cunzai_zsy0531  阅读(19)  评论(0编辑  收藏  举报