Description
给定一个长度为 N + M N+M N+M 的排列。
你每次可以选择前 N N N 个数中的一个以及后 M M M 个数中的一个并将它们交换。
你需要求出,至少需要多少次交换操作才能使得排列变为 1 , 2 , 3 , ⋯ , N + M 1,2,3,\cdots,N+M 1,2,3,⋯,N+M。
Solution
下面,为方便叙述,令位置不超过 N N N 为左边,否则为右边。
假设我们可以交换同属一边的数,那么这就成为了一道裸题: 从 i i i 向 p i p_i pi 连边,得到了一张每个点的入度以及出度均为 1 1 1 的图。考虑其每一个连通块(显然是个环),并交换环上相邻的位置即可。总操作次数为 N + M − K N+M-K N+M−K,其中 K K K 表示连通块的个数。
加入只能交换左右边的限制怎么办呢?我们依然按照 i → p i i \to p_i i→pi 的方式建图。考虑每一个连通块,若它跨越了左边和右边,那么它必然可以在 S − 1 S-1 S−1 次交换中被还原,其中 S S S 表示连通块的大小。特别的,若它只属于左边或只属于右边,那么上述结论不成立。
令只属于左边的连通块有 X X X 个,只属于右边的连通块有 Y Y Y 个。不妨设 X < Y X<Y X<Y。我们可以将左边的前 Y Y Y 个与右边的两两配对并进行操作,且每次操作次数均为 S S S,其中 S S S 表示混合连通块的大小(先随意交换一个位置,然后接下来每次都可以恰好复原一个位置,最后一次可以复原两个位置, 1 + ( S − 1 ) = S 1+(S-1)=S 1+(S−1)=S)。对于那 Y − X Y-X Y−X 个失配的,我们从异侧硬搞过来一个位置与它配对并进行操作。分析一下这两种的优劣——对于混合连通块而言,若 S = x + y S=x+y S=x+y,我们默认操作次数是 x − 1 + y − 1 x-1+y-1 x−1+y−1 然而这里是 x + y x+y x+y,所以总数要加上 2 2 2。对于单个连通块而言,令其大小为 x x x,我们找过来了一个使得 S = x + 1 S=x+1 S=x+1,然而我们默认是 x − 1 x-1 x−1,所以总数也要加上 2 2 2。
综上所述,答案为 N + M − K + 2 max ( 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;
}