7394. 【2021.11.17NOIP提高组联考】排

Description

给定两个 \(0-(N-1)\) 的排列 \(\left\{P_{i}\right\}\)\(\left\{Q_{i}\right\}\) 。要求构造两个 \(0-(N-1)\) 的排列 \(\left\{A_{i}\right\}\)\(\left\{B_{i}\right\}\), 且满足对 \(\forall 0 \leq i<N, A_{i}=P_{i}\)\(i, B_{i}=Q_{i}\)\(i\)

要求最大化 \(A_{i} \neq B_{i}\) 的数量, 输出这个最大值。

\(1\le n\le 10^5\)

Solution

声明:拆环指的是当前位置选择 \(i\) 而不是 \(P_i\) 或者 \(Q_i\)

考虑将 \(P\) 中的循环看做一个整体,因为发现,如果对于环上任意一个位置,将其拆掉,可以推导得出整个环都会被拆。\(Q\) 同理。

同时根据正难则反的思想,将题目转换成出至少有多少个位置 \(A_i=B_i\)

分情况讨论一下什么情况下会产生贡献:

  1. \(P_i=Q_i=i\) 时,这种情况下无论什么情况 \(A_i=B_i\),答案 -1 即可。
  2. \(P_i=i,Q_i\not=i\) 时,将 \(Q_i\) 对应的环拆掉会产生贡献,否则不会。
  3. \(P_i\not=i,Q_i=i\) 时,同上,将 \(P_i\) 对应的环拆掉才会产生贡献,否则不会。
  4. \(P_i\not=i,Q_i\not=i,P_i\not=Q_i\) 时,同时拆掉两个环才会产生贡献,否则不会。
  5. \(P_i\not=i,Q_i\not=i,P_i=Q_i\) 时,同时拆或者同时不拆都会产生贡献。

想到这里,网络流就呼之欲出(或许吧)。

\(P_i\) 对应的环放在左边, \(Q_i\) 对应的环放在右边,设源点 \(S\) 和汇点 \(T\)

根据上述情况连边,连边如下(一一对应):

  1. 不用连边,答案直接减。
  2. \(Q_i\) 对应的环向 \(T\) 连一条流量为 1 的边。
  3. \(S\)\(P_i\) 对应的环连一条流量为 1 的边。
  4. \(Q_i\) 对应的环向 \(P_i\) 对应的环连流量为 1 的边。(避免出现产生一些不合法的答案)
  5. \(P_i\) 对应的环向 \(Q_i\) 对应的环连流量为 1 的边,\(Q_i\) 对应的环也向 \(P_i\) 对应的环连流量为 1 的边。(两条)

然后愉快的套上 \(\mathcal{Dinic}\) 就好了。

Code

#include<queue>
#include<cstdio>
#define N 100005
#define inf 2147483647
using namespace std;
struct node
{
	int to,head,next,flow;
}a[N<<2];
int n,S,T,tot=1,cnt,ans,p[N],q[N],r1[N],r2[N],cur[N],deep[N];
bool bj1[N],bj2[N];
void getring1(int x)
{
	++cnt;
	while (!bj1[x]) r1[x]=cnt,bj1[x]=true,x=p[x];
}
void getring2(int x)
{
	++cnt;
	while (!bj2[x]) r2[x]=cnt,bj2[x]=true,x=q[x];
}
void add(int x,int y,int z)
{
	a[++tot].to=y;a[tot].flow=z;a[tot].next=a[x].head;a[x].head=tot;
	a[++tot].to=x;a[tot].flow=0;a[tot].next=a[y].head;a[y].head=tot;
}
bool bfs()
{
	queue<int> q;
	for (int i=1;i<=cnt;++i)
		deep[i]=0;
	q.push(S);deep[S]=1;
	while (!q.empty())
	{
		int x=q.front();q.pop();
		for (int i=a[x].head;i;i=a[i].next)
		{
			int v=a[i].to;
			if (!deep[v]&&a[i].flow) deep[v]=deep[x]+1,q.push(v);
		}
	}
	return deep[T]!=0;
}
int dfs(int x,int fl)
{
	if (x==T) return fl;
	int res=fl;
	for (int i=cur[x];i;i=a[i].next)
	{
		int v=a[i].to;
		cur[x]=i;
		if (deep[v]==deep[x]+1&&a[i].flow)
		{
			int f=dfs(v,min(res,a[i].flow));
			a[i].flow-=f;a[i^1].flow+=f;res-=f;
			if (!res) break;
		}
	} 
	return fl-res;
} 
int dinic()
{
	int res=0;
	while (bfs())
	{
		for (int i=1;i<=cnt;++i)
			cur[i]=a[i].head;
		res+=dfs(S,inf);
	}
	return res;
}
int main()
{
	freopen("per.in","r",stdin);
	freopen("per.out","w",stdout);
	scanf("%d",&n);
	for (int i=1;i<=n;++i)
		scanf("%d",&p[i]),++p[i];
	for (int i=1;i<=n;++i)
		scanf("%d",&q[i]),++q[i];
	for (int i=1;i<=n;++i)
		if (!bj1[i])
			getring1(i);
	for (int i=1;i<=n;++i)
		if (!bj2[i])
			getring2(i);
	S=++cnt;T=++cnt;
	ans=n;
	for (int i=1;i<=n;++i)
	{
		if (p[i]==q[i]&&p[i]==i) ans--;
		else if (q[i]==i) add(S,r1[i],1);
		else if (p[i]==i) add(r2[i],T,1);
		else if (p[i]==q[i]) add(r2[i],r1[i],1),add(r1[i],r2[i],1);
		else add(r2[i],r1[i],1);
	}
	printf("%d\n",ans-dinic());
	return 0;	
} 
原文地址:https://www.cnblogs.com/Livingston/p/15569525.html