2020牛客暑期多校训练营(第一场) I 1 or 2 一般图的最大匹配+多重匹配拆点

一般图的最大匹配用带花树算法:O(n^3)。具体实现我也不太清楚,但当成黑匣子用即可。

https://www.cnblogs.com/xiongtao/p/11189452.html可以参考这篇博客。

下面说下我的想法:

这题难点在于建图。

如果度数全是1,那么直接套模板就行。

但度数有不为1的情况。

回顾二分图拆点(不会的话看 lyd的算法竞赛-进阶指南上有),简单说下思路:

对于一个二分图的多重匹配:左部点i至多与kl[i]条边相连,右部点j最多与kr[i]条边相连

则这个二分图多重匹配的一个做法是——拆点:

把第i个左部节点拆成kl[i]个不同的左部节点,第j个右部节点拆成kr[j]个有部节点。//(注意原图的n+m个节点保留,这里拆的节点从n+m+1个节点编号开始),对于原图中的每条边(i,j),i连向i拆成的所有点,j连向j拆成的所有点。求解最大匹配即可。

一般图一样拆即可。

扫描二维码关注公众号,回复: 11454168 查看本文章

正确性:

用下上面博客里的这张图。

我们发现,对于一条边i-j,  最终的最大匹配,要么是i-j匹配,要么i与一个点匹配,j与一个点匹配。

前者说明i-j这条边不对点度数产生贡献,即最终选的子图不包括这条边。

后者恰好是i-j这条边让与i匹配的点和与j匹配的点度数都加1;

由于每个点只能被匹配一次,则只有上面两种情况,

即拆点后:每条边最多贡献一次,而左边图每个点有其度数个,若最终最大匹配是完美匹配,则这个图刚好满足每个点的度数条件。

巧妙的拆点方法。

这种遇到一次以后基本都会了。不过二分图的多重匹配一般用最大流做。。

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<queue>
#define inf 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int maxn=1050;
bool g[maxn][maxn],inque[maxn],inpath[maxn];
bool inhua[maxn];
int st,ed,newbase,ans,n;
int base[maxn],pre[maxn],match[maxn];
int head,tail,que[maxn]; 
int x[maxn],y[maxn],f[maxn],mp[maxn][maxn],ne,np;

void Push(int u)
{
    que[tail]=u;
    tail++;
    inque[u]=1;
}
int Pop()
{
    int res=que[head];
    head++;
    return res;
}

int lca(int u,int v)//寻找公共花祖先 
{
    memset(inpath,0,sizeof(inpath));
    while(1)
    {
        u=base[u];
        inpath[u]=1;
        if(u==st) break;
        u=pre[match[u]];    
    }    
    while(1)
    {
        v=base[v];
        if(inpath[v]) break;
        v=pre[match[v]];
    }
    return v;
} 
void reset(int u)//缩环 
{
    int v;
    while(base[u]!=newbase)
    {
        v=match[u];
        inhua[base[u]]=inhua[base[v]]=1;
        u=pre[v];
        if(base[u]!=newbase) pre[u]=v;
    }
} 
void contract(int u,int v)//
{
    newbase=lca(u,v);
    memset(inhua,0,sizeof(inhua));
    reset(u);
    reset(v);
    if(base[u]!=newbase) pre[u]=v;
    if(base[v]!=newbase) pre[v]=u;
    for(int i=1;i<=n;i++)
    {
        if(inhua[base[i]]){
            base[i]=newbase;
            if(!inque[i])
                Push(i);
        }
    }
}
void findaug()
{
    memset(inque,0,sizeof(inque));
    memset(pre,0,sizeof(pre));
    for(int i=1;i<=n;i++)//并查集 
        base[i]=i;
    head=tail=1;
    Push(st);
    ed=0;
    while(head<tail)
    {
        int u=Pop();
        for(int v=1;v<=n;v++)
        {
            if(g[u][v]&&(base[u]!=base[v])&&match[u]!=v)
            {
                if(v==st||(match[v]>0)&&pre[match[v]]>0)//成环 
                    contract(u,v);
                else if(pre[v]==0)
                {
                    pre[v]=u;
                    if(match[v]>0)
                        Push(match[v]);
                    else//找到增广路 
                    {
                        ed=v;
                        return ;    
                    }    
                }
            }
        }
    }
}
void aug()
{
    int u,v,w;
    u=ed;
    while(u>0)
    {
        v=pre[u];
        w=match[v];
        match[v]=u;
        match[u]=v;
        u=w;
    }
}
void edmonds()//匹配 
{
    memset(match,0,sizeof(match));
    for(int u=1;u<=n;u++)
    {
        if(match[u]==0)
        {
            st=u;
            findaug();//以st开始寻找增广路 
            if(ed>0) aug();//找到增广路  重新染色,反向 
        }
    }
}
//以上是带花树求最大匹配算法  不用看 
 
void create()//建图 
{
    n=0;
    memset(g,0,sizeof(g));
    for(int i=1;i<=np;i++)
        for(int j=1;j<=f[i];j++)
            mp[i][j]=++n;//拆点,给每个度的点编号 
    for(int i=0;i<ne;i++)
    {//此时n+1代表x,n+2代表y 
        for(int j=1;j<=f[x[i]];j++)
            g[mp[x[i]][j]][n+1]=g[n+1][mp[x[i]][j]]=1;//每个度的点与对应的x,y相连 
        for(int j=1;j<=f[y[i]];j++)
            g[mp[y[i]][j]][n+2]=g[n+2][mp[y[i]][j]]=1;
        g[n+1][n+2]=g[n+2][n+1]=1;//x与y相连 
        n+=2;
    }    
}
void print()
{
    ans=0;
    for(int i=1;i<=n;i++)
        if(match[i]!=0)
            ans++;
    if(ans==n)    printf("Yes\n");
    else    printf("No\n");
}
int main()
{
    int t,k=0;
    while(~scanf("%d%d",&np,&ne))
    {
    	for(int i=1;i<=np;i++)
            scanf("%d",&f[i]);
        for(int i=0;i<ne;i++)
            scanf("%d%d",&x[i],&y[i]);
        create();
        edmonds();
        print();    
    }     
    return 0;
}

猜你喜欢

转载自blog.csdn.net/bjfu170203101/article/details/107307322
今日推荐