CF1768D Lucky Permutation 题解

CF1768D Lucky Permutation 题解

图论好题。

首先观察最后的排列样子,不难发现是形如一个排列,交换相邻两个数的样子。

例如下图:

\(pos_i\) 表示数字 \(i\)最终排列中的位置。

若没有交换,显然 \(pos_i=i\)

然后设当前数字 \(i\) 的位置为 \(p_i\),计算至少花多少次使得 \(\forall pos_i=p_i\)

是个古老但是经典的 trick:建图,以 \(p_i\)\(pos_i\) 连一条有向边,不难发现每个连通块都是一个环。记录环个数 \(c\),答案就是 \(n-c\)

然后考虑优化。因为要枚举 \(i\)\(i+1\) 进行交换,需枚举 \(n-1\) 次,每次都遍历一遍是不允许的。

发现我们需要连通块的个数越多越好,所以在环里交换,把环断开一定是最优的,且代价一定少 \(1\)

发现每次交换仅影响了最多两个连通块。分类讨论一下,若 \(p_i\)\(p_{i+1}\) 在一个连通块里,那么答案就是 \(n-c-1\),且不可能更小了。

否则相当于把两个环连在一起,答案是 \(n-c+1\),可以当备选答案。

然后就做完了。

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ljl;
const int N=2e5+5;
int n,a[N],T,ans,pos[N],p[N],cnt_e,ehead[N],siz,bl[N],tot;
bool vis[N];
struct E{
	int to,pre;
}e[N<<1];
void adde(int from,int to)
{
	e[++cnt_e].to=to;
	e[cnt_e].pre=ehead[from];
	ehead[from]=cnt_e;
	return;
}
void init()
{
	cnt_e=0;tot=0;
	for(int i=1;i<=n;++i)ehead[i]=0;
	return;
}
void dfs(int u)
{
	vis[u]=1;++siz;bl[u]=tot;
	for(int i=ehead[u];i;i=e[i].pre)
	{
		int v=e[i].to;
		if(vis[v])continue;
		dfs(v);
	}
	return;
}
int calc()
{
	int cnt=0;
	for(int i=1;i<=n;++i)vis[i]=0;
	for(int i=1;i<=n;++i)
	{
		if(vis[i])continue;
		siz=0;++tot;dfs(i);
		cnt+=(siz-1);
	}
	return cnt;
}
void swapPOS(int x,int y)//swap(pos[x],pos[y])
{
	//p[x]->pos[y]
	//p[y]->pos[x]
	for(int i=ehead[p[x]];i;i=e[i].pre)e[i].to=pos[y];
	for(int i=ehead[p[y]];i;i=e[i].pre)e[i].to=pos[x];
	return;
}
int main(){
	ios::sync_with_stdio(0);
	cin>>n;ans=n-1;
	for(int i=1;i<=n;++i)
	{
		cin>>a[i];
		p[a[i]]=i;
	}
	for(int i=1;i<=n;++i)pos[i]=i;
	init();
	for(int i=1;i<=n;++i)
		adde(p[i],pos[i]);
	int t=calc();
	for(int i=1;i<n;++i)
	{
		if(bl[p[i]]!=bl[p[i+1]])ans=min(ans,t+1);
		else{ans=t-1;break;}
	}
	cout<<ans<<'\n';
	return 0;
}

我看看谁不改代码就 copy 了?

posted @ 2026-08-11 16:24  Atserckcn  阅读(9)  评论(0)    收藏  举报