Graph theory 0x06 【二分图】

二分图最大匹配

匈牙利算法
洛谷3386【模板】二分图最大匹配

#include<bits/stdc++.h>
using namespace std;
const int maxn = 503;
int mp[maxn][maxn];

int Left[maxn];// match
bool S[maxn],T[maxn];
int n,m;

bool match(int i){
    S[i]=1;
    for(int j=1;j<=m;++j) if(mp[i][j]&& !T[j]) {
        T[j] = 1;
        if(!Left[j] || match(Left[j])) {
            Left[j] = i;
            return true;
        }
    }
    return false;
}

int KM(){
    int cnt = 0;
    for(int i=1;i<=n;++i){
        for(int j=1;j<=m;++j) S[j]=T[j]=0;
        if(match(i)) cnt++;
    }
    return cnt;
}

int main(){
    int e;
    scanf("%d%d%d",&n,&m,&e);
    for(int i=1;i<=e;++i) {
        int u,v;scanf("%d%d",&u,&v);
        mp[u][v] = 1;
    }
    cout<<KM()<<endl;
}

延伸

二分图最大独立集

一个最大的点的集合,该集合内的任意两点没有边相连。
二分图的最大独立集 = 二分图顶点数 - 二分图最大匹配数。
即 = n - v

二分图最大团

一个最大的点的集合,该集合内的任意两点都有边相连。
二分图的最大独立集等价于二分图补图的最大团。
二分图的最大团等价于二分图补图的最大独立集。

二分图最小顶点覆盖

用最少的点,让每条边都至少和其中一个点关联。
二分图中最小顶点覆盖等于最大匹配数。
最大匹配之后,对于那些没有被归于最大匹配中的点们,他们要么有边连着最大匹配了的点,要么无边。无边则不用覆盖(本来就没有,覆盖什么),如果是连着最大匹配了的点,最大匹配中取那个最大匹配了的点构成最小顶点覆盖的集合元素即可。假设那个点的匹配点要成为构成元素,说明有另一外面的点连着该匹配点,那么就会有增光路。因此这个假设不成立。

二分图最小边覆盖

实质是个边集,这个集合里的边能覆盖所有的点,最小边覆盖是满足这个要求的所有边集中边数最少的一个。
二分图中最小边覆盖=顶点数-最小顶点覆盖(最大匹配)。
这里顶点数等于总的顶点数,是二分图两边的顶点数,不是一边。
证明:(来源)设最大匹配数为m,总顶点数为n。为了使边数最少,又因为一条边最多能干掉两个点,所以尽量用边干掉两个点。也就是取有匹配的那些边,当然这些边是越多越好,那就是最大匹配了,所以先用最大匹配数目的边干掉大多数点。剩下的解决没有被匹配的点,就只能一条边干掉一个点了,设这些数目为a,显然,2m+a=n,而最小边覆盖=m+a,所以最小边覆盖=(2m+a)-m=n-m。

二分图最大权匹配

KM(Hungarian Algorithm(Kuhn-Munkres Algorithm))算法
洛谷板子

#include <bits/stdc++.h>
using namespace std;
const int maxn = 510;
#define int long long
int n,m;
const int inf = 1000000000000;
int W[maxn][maxn];

int Lx[maxn], Ly[maxn];
int Left[maxn], Right[maxn];
bool S[maxn], T[maxn];
int slack[maxn], pre[maxn];

queue<int> q;

void aug(int v){
    int t;
    while(v){
        t = Right[pre[v]];
        Right[pre[v]] = v;
        Left[v] = pre[v];
        v = t;
    }
}

void bfs(int s){
    memset(S,0,sizeof(S));
    memset(T,0,sizeof(T));
    fill(slack+1,slack+1+n,inf);

    while(!q.empty()) q.pop();
    q.push(s);

    while(1){
        while(!q.empty()){
            int i = q.front();q.pop();
            S[i] = 1;
            for(int j=1;j<=n;++j) if(!T[j]){
                    if(Lx[i]+Ly[j]-W[i][j]<slack[j]){
                        slack[j]=Lx[i]+Ly[j]-W[i][j];
                        pre[j]=i;
                        if(slack[j]==0){
                            T[j] = 1;
                            if(!Left[j]){aug(j);return;}
                            else q.push(Left[j]);
                        }
                    }
                }
        }
        int d=inf;
        for(int i=1;i<=n;++i) if(!T[i]) d=min(d,slack[i]);
        for(int i=1;i<=n;++i){
            if(S[i]) Lx[i] -= d;
            if(T[i]) Ly[i] += d;
            else slack[i] -= d;
        }
        for(int i=1;i<=n;++i) if(!T[i]){
                if(slack[i]==0){
                    T[i] = 1;
                    if(!Left[i]) {aug(i);return;}
                    else q.push(Left[i]);
                }
            }
    }
}

int32_t main(){
    scanf("%lld%lld",&n,&m);

    for(int i=1;i<=n;++i)
        for(int j=1;j<=n;++j)
            W[i][j] = -inf;

    for(int i=1;i<=m;++i){
        int y,c,h;scanf("%lld%lld%lld",&y,&c,&h);
        W[y][c] = max(h,W[y][c]);
    }

    fill(Lx+1,Lx+1+n,-inf);

    for(int i=1;i<=n;++i)
        for(int j=1;j<=n;++j)
            Lx[i] = max(Lx[i],W[i][j]);

    for(int i=1;i<=n;++i) bfs(i);

    long long ans = 0;
    for(int i=1;i<=n;++i){
        ans += W[Left[i]][i];
    }
    printf("%lld\n",ans);
    for(int i=1;i<=n;++i){
        printf("%lld%c",Left[i],i==n?'\n':' ');
    }

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值