图论:一般图最大匹配(带花树)

模板:直接求一般图最大匹配:

同时求出match[i]表示与第i个节点匹配的点是哪一个

点击查看折叠代码块
#include <bits/stdc++.h>
using namespace std;
const int maxn=1010;
const int inf=0x3f3f3f3f;
typedef long long ll;

int n,m;
int mp[maxn][maxn];
int f[maxn];//子图中每个点需要达到的度数
int x[maxn],y[maxn];//原图的边对应的两点
int deg[maxn];
int fa[maxn];//并查集
int pre[maxn],match[maxn];
int st,ed,newfa,ans,ct=0;
bool g[maxn][maxn],inque[maxn],inpath[maxn];//新图的邻接矩阵,是否在队列中,是否在增广路中
bool inhua[maxn];//点是否在花内

int head,tail;
int que[maxn];

void _push(int x){
    que[tail++]=x;
}

int _pop(){
    int x=que[head++];
    return x;
}

int lca(int u,int v){//朴素法找最近公共祖先
    memset(inpath,0,sizeof(inpath));
    while(1){
        u=fa[u];//u变成u的祖先
        inpath[u]=1;
        if(u==st) break;
        u=pre[match[u]];
    }
    while(1){
        v=fa[v];
        if(inpath[v]) break;
        v=pre[match[v]];
    }
    return v;
}

void reset(int u){//缩环
    int v;
    while(fa[u]!=newfa){
        v=match[u];
        inhua[fa[u]]=inhua[fa[v]]=1;
        u=pre[v];
        if(fa[u]!=newfa) pre[u]=v;
    }
}

void contract(int u,int v){
    newfa=lca(u,v);
    memset(inhua,0,sizeof(inhua));
    reset(u);
    reset(v);
    if(fa[u]!=newfa) pre[u]=v;
    if(fa[v]!=newfa) pre[v]=u;
    for (int i=1;i<=ct;i++){
        if(inhua[fa[i]]){
            fa[i]=newfa;
            if(!inque[i]){
                inque[i] = 1;
                _push(i);
            }
        }
    }
}

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 findaug(){//找增广路
    memset(inque,0,sizeof(inque));
    memset(pre,0,sizeof(pre));
    for(int i=1;i<=ct;i++) fa[i]=i;//初始化并查集

    head=tail=1;
    _push(st);
    ed=0;
    while(head<tail){
        int u=_pop();
        for (int v=1;v<=ct;v++){
            if(g[u][v] && (fa[u]!=fa[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 edmonds(){//带花树,求匹配
    memset(match,0,sizeof(match));
    for (int u=1;u<=ct;u++){//对于新图中每个点
        if(match[u]==0){//如果该点未匹配过
            st=u;
            findaug();//以st开始寻找增广路
            if(ed>0) aug();//找到增广路,重新染色,反向
        }
    }
}
//以上是带花树求最大匹配算法,可以作为一个板子

int main(){
    ct = 0;
    cin>>n>>m;
    for (int i=1;i<=m;i++){
        int u,v;
        scanf("%d%d",&u,&v);
        g[u][v] = g[v][u] = 1;
    }
    ct = n;//ct记录的是图点的总个数,根据题意可能需要拆点
    edmonds();
    ans = 0;
    for (int i=1;i<=ct;i++){
        if(match[i]!=0){
            // cout<<"i = "<<i<<" match = "<<match[i]<<endl;
            ans++;
        }
    }
    int Maxmatch = ans/2;
    cout<<Maxmatch<<endl;
    return 0;
}

/*
6 6
1 2
2 3
3 4
4 5
1 5
4 6

i = 1 match = 5
i = 2 match = 3
i = 3 match = 2
i = 4 match = 6
i = 5 match = 1
i = 6 match = 4
3
*/

例题:hdu3551

点击查看折叠代码块
/*
2020-7-14

hdu-3551,子图点度数为任意

一般图匹配_Edmond's Algorithm

建图:将每个点根据f[i]拆成f[i]个点。
将每一条边拆成两个点,将这条边关联的两个点的拆点分别与边的拆点相连,还有边的拆点也要相连:

如 1-2 这条边,假设f[1]=1,f[2]=2,
则我们将1号点拆成点1,2号点拆成点2和点3,1-2这条边拆成两个点4和点5,将点1与点4相连,点2和点3都与点5相连,点4与点5相连

建图完成后,对这个图跑带花树找到匹配,如果这个匹配是完美匹配,则说明存在子图。
参考:https://www.cnblogs.com/xiongtao/p/11189452.html
*/
#include <bits/stdc++.h>
using namespace std;
const int maxn=1010;
const int inf=0x3f3f3f3f;
typedef long long ll;

int n,m;
int mp[maxn][maxn];
int f[maxn];//子图中每个点需要达到的度数
int x[maxn],y[maxn];//原图的边对应的两点
int deg[maxn];
int fa[maxn];//并查集
int pre[maxn],match[maxn];
int st,ed,newfa,ans,ct=0;
bool g[maxn][maxn],inque[maxn],inpath[maxn];//新图的邻接矩阵,是否在队列中,是否在增广路中
bool inhua[maxn];//点是否在花内

int head,tail;
int que[maxn];

void _push(int x){
    que[tail++]=x;
}

int _pop(){
    int x=que[head++];
    return x;
}

int lca(int u,int v){//朴素法找最近公共祖先
    memset(inpath,0,sizeof(inpath));
    while(1){
        u=fa[u];//u变成u的祖先
        inpath[u]=1;
        if(u==st) break;
        u=pre[match[u]];
    }
    while(1){
        v=fa[v];
        if(inpath[v]) break;
        v=pre[match[v]];
    }
    return v;
}

void reset(int u){//缩环
    int v;
    while(fa[u]!=newfa){
        v=match[u];
        inhua[fa[u]]=inhua[fa[v]]=1;
        u=pre[v];
        if(fa[u]!=newfa) pre[u]=v;
    }
}

void contract(int u,int v){
    newfa=lca(u,v);
    memset(inhua,0,sizeof(inhua));
    reset(u);
    reset(v);
    if(fa[u]!=newfa) pre[u]=v;
    if(fa[v]!=newfa) pre[v]=u;
    for (int i=1;i<=ct;i++){
        if(inhua[fa[i]]){
            fa[i]=newfa;
            if(!inque[i]){
                inque[i] = 1;
                _push(i);
            }
        }
    }
}

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 findaug(){//找增广路
    memset(inque,0,sizeof(inque));
    memset(pre,0,sizeof(pre));
    for(int i=1;i<=ct;i++) fa[i]=i;//初始化并查集

    head=tail=1;
    _push(st);
    ed=0;
    while(head<tail){
        int u=_pop();
        for (int v=1;v<=ct;v++){
            if(g[u][v] && (fa[u]!=fa[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 edmonds(){//带花树,求匹配
    memset(match,0,sizeof(match));
    for (int u=1;u<=ct;u++){//对于新图中每个点
        if(match[u]==0){//如果该点未匹配过
            st=u;
            findaug();//以st开始寻找增广路
            if(ed>0) aug();//找到增广路,重新染色,反向
        }
    }
}
//以上是带花树求最大匹配算法,可以作为一个板子


void create(){//建图
    ct=0;
    memset(g,0,sizeof(g));
    for (int i=1;i<=n;i++){//对于每个点的度数进行拆点
        for (int j=1;j<=f[i];j++){
            mp[i][j] = ++ct;//原图中点i拆成f[i]个点的编号
        }
    }

    for (int i=1;i<=m;i++){//对于每条边拆成两个点,ct+1拆成x,ct+2拆成y
        for (int j=1;j<=f[x[i]];j++){//点拆点和边拆点相连,这条边的第一个点为x[i],f[x[i]]表示拆成的点的个数,mp[x[i]][j]表示x[i]这个点拆成的第j个点的编号
            g[ mp[x[i]][j] ][ct+1] = g[ct+1][ mp[x[i]][j] ]=1;
        }
        for (int j=1;j<=f[y[i]];j++){//同上
            g[ mp[y[i]][j] ][ct+2] = g[ct+2][ mp[y[i]][j] ]=1;
        }
        g[ct+1][ct+2]=g[ct+2][ct+1]=1;//边拆点相连
        ct+=2;
    }
}

void print(){
    ans=0;
    for (int i=1;i<=ct;i++){//看匹配中点数
        if(match[i]!=0){//如果这个点被匹配到了
            ans++;
        }
    }

    if(ans==ct){//如果匹配是一个完美匹配
        printf("YES\n");
    }
    else printf("NO\n");
}

int main(){
    int t,k=0;
    scanf("%d",&t);
    while(t--){
        scanf("%d%d",&n,&m);
        for (int i=1;i<=m;i++){
            scanf("%d%d",&x[i],&y[i]);
        }
        for (int i=1;i<=n;i++){
            scanf("%d",&f[i]);
        }
        printf("Case %d: ",++k);
        create();
        edmonds();
        print();
    }
    return 0;
}
posted @ 2020-07-28 20:31  wsl_lld  阅读(172)  评论(0编辑  收藏  举报