AGC038F Two Permutations gxy001

众所周知,一个排列可以拆成很多个环,我们设 $P_i$ 属于的环为 $p_i$,$Q_i$ 属于的环为 $q_i$。

对于一个环,只有两种选择,拆成若干自环或保留原状。

我们发现情况分为 $5$ 种:

  • $P_i=Q_i=i$,此时必然 $A_i=B_i$。
  • $P_i=i,Q_i\ne i$,当且仅当 $q_i$ 被拆分时 $A_i=B_i$。
  • $P_i\ne i,Q_i=i$,当且仅当 $p_i$ 被拆分时 $A_i=B_i$。
  • $P_i\ne Q_i,P_i\ne i,Q_i\ne i$,当且仅当 $p_i$ 和 $q_i$ 都被拆分时 $A_i=B_i$。
  • $P_i=Q_i\ne i$,当且仅当 $p_i$ 和 $q_i$ 都被拆分或都不被拆分时 $A_i=B_i$。

我们设 $p_i$ 被拆分时在 $S$ 集合中,$q_i$ 被拆分时在 $T$ 集合中,建出最小割模型。

  • $P_i=Q_i=i$,必定耗费 $1$ 代价。
  • $P_i=i,Q_i\ne i$,$q_i$ 在 $T$ 集合中耗费 $1$ 代价,$S$ 向 $q_i$ 连边权为 $1$ 的边。
  • $P_i\ne i,Q_i=i$,$p_i$ 在 $S$ 集合中耗费 $1$ 代价,$p_i$ 向 $T$ 连边权为 $1$ 的边。
  • $P_i\ne Q_i,P_i\ne i,Q_i\ne i$,$p_i$ 在 $S$ 且 $q_i$ 在 $T$ 耗费 $1$ 代价,$p_i$ 向 $q_i$ 连边权为 $1$ 的边。
  • $P_i=Q_i\ne i$,$p_i$ 和 $q_i$ 在不同集合耗费 $1$ 代价,$p_i$ 和 $q_i$ 互连边权为 $1$ 的边。

然后跑最小割即可。

这里简单描述一下最后两种边为什么要这么连,以第四种情况为例,若 $S$ 能到 $p_i$,$q_i$ 能到 $T$,为了割开集合,就必须断这条边,也就是说只有断掉这条边才有可能 $p_i$ 在 $S$ 集合中,$q_i$ 在 $T$ 集合中。

因为是最小割,所以第五种情况的边在任何情况下最多只会割掉一个方向,所以代价是 $1$,没有问题。

然后是复杂度,由于是二分图单位边权,时间复杂度 $\mathrm O(n\sqrt n)$。

#include<cstdio>
#include<algorithm>
int ans,n,a[100010],b[100010],q[100010],p[100010],tp[100010],tq[100010],cnt,S,T;
int head[100010],to[200010],nxt[200010],w[200010]; 
void add(int const &x,int const &y,int const &wy,int const &wx){
	static int cnt=1;
	to[++cnt]=y,w[cnt]=wy,nxt[cnt]=head[x],head[x]=cnt;
	to[++cnt]=x,w[cnt]=wx,nxt[cnt]=head[y],head[y]=cnt;
}
int d[100010],que[100010],*hd,*tl,cur[100010];
bool bfs(){
	for(int i=1;i<=cnt;i++)d[i]=cnt;
	d[S]=0;
	hd=tl=que;
	*tl++=S;
	while(tl!=hd){
		int x=*hd++;
		for(int i=head[x];i;i=nxt[i])
			if(w[i]&&d[to[i]]==cnt) d[*tl++=to[i]]=d[x]+1;
	}
	return d[T]!=cnt;
}
int dfs(int const &x,int const &low){
	if(x==T) return low;
	int used=0;
	for(int &i=cur[x];i;i=nxt[i])
		if(w[i]&&d[to[i]]-1==d[x]){
			int res=dfs(to[i],std::min(w[i],low-used));
			if(res) w[i]-=res,w[i^1]+=res,used+=res;
			if(used==low) return used;
		}
	return used;
}
int dinic(){
	int ans=0;
	while(bfs()){
		for(int i=1;i<=cnt;i++)cur[i]=head[i];
		ans+=dfs(S,0x3f3f3f3f);
	}
	return ans;
}
int main(){
	scanf("%d",&n);
	ans=n;
	for(int i=1;i<=n;i++)scanf("%d",a+i),++a[i];
	for(int i=1;i<=n;i++)scanf("%d",b+i),++b[i];
	for(int i=1;i<=n;i++){
		if(!p[i]){
			p[i]=i;
			for(int x=a[i];x!=i;x=a[x])p[x]=i;
			if(a[i]!=i) tp[i]=++cnt;
		}
		if(!q[i]){
			q[i]=i;
			for(int x=b[i];x!=i;x=b[x])q[x]=i;
			if(b[i]!=i) tq[i]=++cnt;
		}
	}
	S=++cnt,T=++cnt;
	for(int i=1;i<=n;i++){
		if(a[i]==i&&b[i]==i)--ans;
		else if(a[i]!=i&&b[i]!=i){
			if(a[i]==b[i]) add(tp[p[i]],tq[q[i]],1,1);
			else add(tp[p[i]],tq[q[i]],1,0);
		}else{
			if(a[i]==i) add(S,tq[q[i]],1,0);
			else add(tp[p[i]],T,1,0);
		}
	}
	printf("%d\n",ans-dinic());
	return 0;
}