[CF1491G]Switch and Flip

Switch and Flip

题解

我们可以先将原序列转化成一张有向图,点 i i i ∣ a i ∣ \left |a_{i}\right | ai连边。

我们的每次操作相当于交换两个点的出边所连向的点,并在两点颜色相同时将它们的颜色全部翻转。

相当于有这样的操作:

image-20210522121645400

image-20210522121702163

转化后的图必然有许多的环,我们的任务是将所有的环都变成自环。

如果我们有两个以上的环,我们有这样的解决方案:

image-20210522122307940

将两个环连在一起,然后通过操作不断缩小环的大小,将所有的为翻转点都变成自环,最后就只有两个反转点连成的环,用一次操作将它解决的,这样的操作数是 O ( n ) O\left(n\right) O(n)的。

如果环数量最后不够,我们可以拿一个自环来代替,这样操作数是 O ( n + 1 ) O\left(n+1\right) O(n+1)的。

但如果总共只有 1 1 1个大小为 n n n的环呢?

  • 对于 n = 2 n=2 n=2的情况,直接翻一下就行了。

  • 对于 n = 3 n=3 n=3的情况我们可以手玩一下,操作数是 O ( 4 ) O(4) O(4)

  • 对于 n > 3 n>3 n>3的情况,我们可以先交换环上相邻的两个点,将其变成两个只有一个染色点的环。

    我们将其变成一个只有一个点染色的自环,一个有一个点染色一个点未染色的二元环。

    我们再交换这两个环上的点,将其转化成 n = 3 n=3 n=3的情况,照样处理即可。

    这样的操作数也是 O ( n + 1 ) O\left(n+1\right) O(n+1)的。

总时间复杂度 O ( n ) O\left(n\right) O(n),毕竟都是处理环的翻转嘛。

源码

void dosaka(int u){vis[u]=1;vec[idx].push_back(u);if(!vis[c[u]])dosaka(c[u]);}
void Swap(int u,int v){swap(c[u],c[v]);ans.push_back(mkpr(u,v));}
signed main(){
	read(n);for(int i=1;i<=n;i++)read(c[i]);
	for(int i=1;i<=n;i++)if(!vis[i]&&c[i]!=i)idx++,dosaka(i);
	for(int i=2;i<=idx;i+=2){
		int u=vec[i][0],v=vec[i-1][0];Swap(u,v);
		while(c[u]!=v)Swap(u,c[u]);
		while(c[v]!=u)Swap(v,c[v]);
		Swap(u,v);
	}
	if(idx&1){
		if(vec[idx].size()<n){
			for(int i=1;i<=n;i++){
				if(c[i]!=i)continue;int u=vec[idx][0];Swap(i,u);
				while(c[i]!=u){int las=c[i];Swap(i,c[i]);}Swap(u,i);break;
			}
		}
		if(vec[idx].size()==n){
			if(n==2)Swap(vec[idx][0],vec[idx][1]);
			if(n==3){
				Swap(vec[idx][0],vec[idx][1]);
				Swap(vec[idx][1],vec[idx][2]);
				Swap(vec[idx][0],vec[idx][2]);
				Swap(vec[idx][0],vec[idx][1]);
			}
			if(n>3){
				Swap(vec[idx][0],vec[idx][1]);
				while(c[c[vec[idx][0]]]!=vec[idx][0])Swap(vec[idx][0],c[vec[idx][0]]);
				Swap(c[vec[idx][0]],vec[idx][1]);
				Swap(vec[idx][0],c[vec[idx][0]]);
				Swap(vec[idx][0],vec[idx][1]);
			}
		}
	}
	printf("%d\n",ans.size());
	for(int i=0;i<ans.size();i++)
		printf("%d %d\n",ans[i].fir,ans[i].sec);
	return 0;
}

谢谢!!!

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值