zoukankan      html  css  js  c++  java
  • 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;	
    } 
    
  • 相关阅读:
    LeetCode | Divide Two Integers
    LeetCode | Pow(x, n)
    LeetCode | Sqrt (x)
    LeetCode | 3 Sum
    LeetCode | Two Sum
    LeetCode | Pascal's Triangle II
    nodejs eclipse
    CentOS: Make Command not Found and linux xinetd 服务不能启动
    jquery将form表单序列化常json
    VMware Mac OS补丁安装
  • 原文地址:https://www.cnblogs.com/Livingston/p/15569525.html
Copyright © 2011-2022 走看看