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 了?

浙公网安备 33010602011771号