Description
给定一个长度为 N+MN+MN+M 的排列。
你每次可以选择前 NNN 个数中的一个以及后 MMM 个数中的一个并将它们交换。
你需要求出,至少需要多少次交换操作才能使得排列变为 1,2,3,⋯ ,N+M1,2,3,\cdots,N+M1,2,3,⋯,N+M。
Solution
下面,为方便叙述,令位置不超过 NNN 为左边,否则为右边。
假设我们可以交换同属一边的数,那么这就成为了一道裸题: 从 iii 向 pip_ipi 连边,得到了一张每个点的入度以及出度均为 111 的图。考虑其每一个连通块(显然是个环),并交换环上相邻的位置即可。总操作次数为 N+M−KN+M-KN+M−K,其中 KKK 表示连通块的个数。
加入只能交换左右边的限制怎么办呢?我们依然按照 i→pii \to p_ii→pi 的方式建图。考虑每一个连通块,若它跨越了左边和右边,那么它必然可以在 S−1S-1S−1 次交换中被还原,其中 SSS 表示连通块的大小。特别的,若它只属于左边或只属于右边,那么上述结论不成立。
令只属于左边的连通块有 XXX 个,只属于右边的连通块有 YYY 个。不妨设 X<YX<YX<Y。我们可以将左边的前 YYY 个与右边的两两配对并进行操作,且每次操作次数均为 SSS,其中 SSS 表示混合连通块的大小(先随意交换一个位置,然后接下来每次都可以恰好复原一个位置,最后一次可以复原两个位置,1+(S−1)=S1+(S-1)=S1+(S−1)=S)。对于那 Y−XY-XY−X 个失配的,我们从异侧硬搞过来一个位置与它配对并进行操作。分析一下这两种的优劣——对于混合连通块而言,若 S=x+yS=x+yS=x+y,我们默认操作次数是 x−1+y−1x-1+y-1x−1+y−1 然而这里是 x+yx+yx+y,所以总数要加上 222。对于单个连通块而言,令其大小为 xxx,我们找过来了一个使得 S=x+1S=x+1S=x+1,然而我们默认是 x−1x-1x−1,所以总数也要加上 222。
综上所述,答案为 N+M−K+2max(X,Y)N+M-K+2\max(X,Y)N+M−K+2max(X,Y)。
时间复杂度 O(n)O(n)O(n),本题被解决。
Code
#include <bits/stdc++.h>
using namespace std;
const int maxl=200005;
int read(){
int s=0,w=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') w=-w;ch=getchar();}
while (ch>='0'&&ch<='9'){s=(s<<1)+(s<<3)+(ch^'0');ch=getchar();}
return s*w;
}
int n,m,len,k,x,y;
int a[maxl],fa[maxl],cntl[maxl],cntr[maxl];
int Find(int x){
if (x==fa[x]) return x;
else return fa[x]=Find(fa[x]);
}
signed main(){
n=read(),m=read();len=n+m;
for (int i=1;i<=len;i++){
a[i]=read(),fa[i]=i;
if (i<=n) cntl[i]=1;
else cntr[i]=1;
}
for (int i=1;i<=len;i++){
int fu=Find(i),fv=Find(a[i]);
if (fu!=fv){
fa[fu]=fv;
cntl[fv]+=cntl[fu];
cntr[fv]+=cntr[fu];
}
}
for (int i=1;i<=len;i++){
if (i==Find(i)){
k++;
if (cntl[i]+cntr[i]==1) continue;
if (cntr[i]==0) x++;
if (cntl[i]==0) y++;
}
}
cout<<len-k+2*max(x,y)<<endl;
return 0;
}

这篇博客探讨了一种排列问题,其中涉及到在给定的排列中,通过选择前N个数中的一个和后M个数中的一个进行交换,来达到目标排列1,2,3,...,N+M。作者首先介绍了没有交换限制时的解决方案,然后讨论了当限制交换必须在排列的左右两侧之间进行时的情况。通过对图论建模和分析连通块,得出了解决问题的算法,时间复杂度为O(n)。最后,提供了C++代码实现来解决这个问题。
840

被折叠的 条评论
为什么被折叠?



