AT5203 [AGC038F] Two Permutations 题解

ATcoder
Luogu

Description.

给定两个长度为 \(n\) 的排列 \(P,Q\),你需要构造两个排列 \(A,B\),满足

  • \(\forall i\in[1,n],A_i=P_i\lor A_i=i\)
  • \(\forall i\in[1,n],B_i=Q_i\lor B_i=i\)

最大化 \(\sum_{i=1}^n[A_i\ne B_i]\),输出最大值。

Solution.

刚开始想错了一步,导致卡住了。
否则大概能想到网络流建模需求那里。
刚开始觉得某个排列的环是可以只拆出一个元素的,但是如果这样至少有一个不满足 \(P_i=A_i\)

就你对于每个排列,拆环
每个环要么被拆成自环要么不变。
分讨

  1. \(A_i=i,B_i=i\):恒为 \(0\)
  2. \(A_i\ne i,B_i=i\)\(pid_i\) 肢解使答案减少 \(1\)
  3. \(A_i=i,B_i\ne i\)\(qid_i\) 肢解使答案减少 \(1\)
  4. \(A_i\ne i,B_i\ne i,a_i\ne b_i\):都肢解使答案减少 \(1\)
  5. \(A_i\ne i,B_i\ne i,a_i\ne b_i\):都肢解或都不肢解使答案减少 \(1\)

就这种限制类型为要么一边要么另一边的基本就想到两个东西:2-sat 和 最小割。
2-sat 是判断可行性的问题,没法最优化,所以这题应该是最小割。

考虑限制类型:相同答案减一,看上去很难做。
考虑有无特殊限制:每次相同答案减一限制连边,会构成二分图。
这启发我们翻转一部的状态,定义一个割集中某个环 \(\in S\) 当且仅当

  • 若它是 \(p\) 的环,且它被肢解了
  • 若它是 \(q\) 的环,且它没被肢解

这样就可以做了,以上连边分别是

  1. 答案减 \(1\)
  2. \(pid_i\rightarrow T\)(它被分在 \(S\) 集合答案少 \(1\)
  3. \(S\rightarrow qid_i\)(它被分在 \(T\) 集合答案少 \(1\)
  4. \(pid_i\rightarrow qid_i\)\(pid_i\) 被分在 \(S\)\(qid_i\) 被分在 \(T\) 答案少 \(1\)
  5. \(pid_i\rightarrow qid_i,qid_i\rightarrow pid_i\)\(pid_i,qid_i\) 集合不同答案少 \(1\)

然后直接最小割就行了。

Coding.

点击查看代码
//是啊,你就是那只鬼了,所以被你碰到以后,就轮到我变成鬼了{{{
#include<bits/stdc++.h>
using namespace std;typedef long long ll;
template<typename T>inline void read(T &x)
{
	x=0;char c=getchar(),f=0;
	for(;c<48||c>57;c=getchar()) if(!(c^45)) f=1;
	for(;c>=48&&c<=57;c=getchar()) x=(x<<1)+(x<<3)+(c^48);
	f?x=-x:x;
}
template<typename T,typename...L>inline void read(T &x,L&...l) {read(x),read(l...);}/*}}}*/
namespace Flow//{{{
{
	struct edge{int to,w,nxt;}e[10000005];int et=1,head[200005],d[200005],cur[200005];
	inline void ADDE(int x,int y,int w) {e[++et]=(edge){y,w,head[x]},head[x]=et;}
	inline void adde(int x,int y,int w) {ADDE(x,y,w),ADDE(y,x,0);}
	inline char bfs(int s,int t)
	{
		int hd=1,tl=1;cur[1]=s,memset(d,0,sizeof(d)),d[s]=1;
		for(int x=s;hd<=tl;x=cur[++hd]) for(int i=head[x];i;i=e[i].nxt)
			if(!d[e[i].to]&&e[i].w) {d[cur[++tl]=e[i].to]=d[x]+1;if(e[i].to==t) return 1;}
		return 0;
	}
	inline int dfs(int x,int t,int lim=1e9)
	{
		int f=lim;if(x==t) return lim;
		for(int &i=cur[x];i;i=e[i].nxt) if(d[e[i].to]==d[x]+1&&e[i].w)
			{int g=dfs(e[i].to,t,min(f,e[i].w));e[i].w-=g,e[i^1].w+=g,f-=g;if(!f) break;}
		return lim-f;
	}
	inline int dinic(int s,int t) {int r=0;while(bfs(s,t)) memcpy(cur,head,sizeof(cur)),r+=dfs(s,t);return r;}
}//}}}
int n,p[100005],q[100005],ip[100005],iq[100005],idt=0;
int main()
{
	read(n);for(int i=1;i<=n;i++) read(p[i]),p[i]++;
	int r=n;for(int i=1;i<=n;i++) read(q[i]),q[i]++;
	for(int i=1;i<=n;i++) if(!ip[i]&&p[i]!=i) {ip[i]=++idt;for(int x=i;p[x]!=i;x=p[x]) ip[p[x]]=idt;}
	for(int i=1;i<=n;i++) if(!iq[i]&&q[i]!=i) {iq[i]=++idt;for(int x=i;q[x]!=i;x=q[x]) iq[q[x]]=idt;}
	int s=idt+1,t=s+1;//分在 s:p 肢解或 q 不肢解
	for(int i=1;i<=idt;i++) Flow::adde(s,i,1),Flow::adde(i,t,1);
	for(int i=1;i<=n;i++)
		if(p[i]==i&&q[i]==i) r--;
		else if(p[i]==i) Flow::adde(s,iq[i],1);//如果 q 肢解(T)答案-1
		else if(q[i]==i) Flow::adde(ip[i],t,1);//如果 p 肢解(S)答案-1
		else if(p[i]!=q[i]) Flow::adde(ip[i],iq[i],1);//都肢解 (1S 1T) 答案-1
		else Flow::adde(ip[i],iq[i],1),Flow::adde(iq[i],ip[i],1);//都肢解答案-1,都不肢解答案-1
	return printf("%d\n",r-Flow::dinic(s,t)+idt),0;
}
posted @ 2021-11-15 11:48  Peal_Frog  阅读(50)  评论(0编辑  收藏  举报