[学习笔记]二分图匹配与匈牙利算法

前言

今天模拟赛T1二分图匹配板子题,但是我不会,于是就全场就我没AT1系列了,赶紧补坑

算法

主要了解两个概念"交替路","增广路".我们所做的就是不断找增广路.图我太懒不想画...推荐一个我认为写的很好的一篇博客,我就是在这学的

https://www.renfei.org/blog/bipartite-matching.html

定理

  • 最小点覆盖数等于二分图最大匹配数

    证明见Matrix67大神Blog:http://www.matrix67.com/blog/archives/116

  • 最大独立集点数数(点集不包含任何一条边)等于总点数减去二分图最大匹配数

  • DAG上的最小不相交路径覆盖(找出最少的经过顶点各不同的路径覆盖整个图)数等于点数减去最大匹配数

这些还是比较有用的

代码

BFS版本

int pre[maxn],match[maxn],vis[maxn];
inline void hungarian(){
    queue <int>q;
    int s,t,u,v,tmp;
    bool flag=0;
    for(ri i=1;i<=n*2;i++)pre[i]=match[i]=-1,vis[i]=0;
    for(ri o=1;o<=n;o++){
        if(match[o]!=-1)continue;
        while(q.size())q.pop();
        pre[o]=-1,q.push(o),flag=0;
        while(q.size()&&!flag){
            u=q.front();q.pop();
            for(ri i=h[u];i&&!flag;i=edge[i].ne){//注意退出
                v=edge[i].to;
                if(vis[v]==o)continue;
                vis[v]=o;
                if(match[v]!=-1){
                    q.push(match[v]);
                    pre[match[v]]=u;
                }
                else{
                    flag=1;
                    s=u,t=v;
                    while(s!=-1){
                        tmp=match[s];
                        match[s]=t,match[t]=s;
                        s=pre[s],t=tmp;
                    }
                }
            }
        }
        if(match[o]!=-1)ans++;
    }
    printf("%d\n",ans);
    return ;
}

注意几点:

  • 我们找到一条增广路后要及时特判退出

  • 用tmp记录s的另一个匹配
  • 一开始起点pre赋为-1

例(水)题:

JZOJ5934 phalanx

经典模型,行为左边的点集,列为右边的,对于不能染两次的行列连边,容易发现连了边的点一起选是非法的,我们要找的就是选出最多的点集使得没有一条边,转化成最大独立集来做

代码:

const int maxn=4005;
const int inf=0x7fffffff;
struct Edge{
    int ne,to;
}edge[maxn<<2];
int h[maxn<<1],num_edge=1;
inline void add_edge(int f,int to){
    edge[++num_edge].ne=h[f];
    edge[num_edge].to=to;
    h[f]=num_edge;
}
int match[maxn<<1],pre[maxn<<1];
int vis[maxn<<1];
int n,num;
inline void solve(){
    int u,v,s,t,tmp,ans=0;
    bool flag=0;
    queue <int> q;
    for(ri i=1;i<=n*2;i++)vis[i]=0,match[i]=pre[i]=-1;
    for(ri o=1;o<=n;o++){
        if(match[o]==-1){
            while(q.size())q.pop();
            q.push(o);
            pre[o]=-1,flag=0;
            while(q.size()&&!flag){
                u=q.front();q.pop();
                //vis[u]=1;
                for(ri i=h[u];i&&!flag;i=edge[i].ne){
                    v=edge[i].to;
                    if(vis[v]==o)continue;
                    vis[v]=o;
                    //printf("--%d %d--\n",u,v);
                    if(match[v]!=-1){
                        q.push(match[v]);
                        pre[match[v]]=u;
                    }
                    else{
                        flag=1;
                        s=u,t=v;
                        while(s!=-1){
                            tmp=match[s];
                            match[s]=t,match[t]=s;
                            t=tmp,s=pre[s];
                        }
                    }
                }   
            }
        }
        if(match[o]!=-1)ans++;
    }
    //printf("%d %d\n",n*2,ans);
    printf("%d\n",(n*2-ans)*n);
}
int main(){
    int x,y;
    freopen("phalanx.in","r",stdin);
    freopen("phalanx.out","w",stdout);
    read(n),read(num);
    for(ri i=1;i<=num;i++){
        read(x),read(y);
        add_edge(x,y+n);
        add_edge(y+n,x);
    }
    solve();
    return 0;
}
JZOJ1922 小行星

像上题一样对于小行星所在行列连边,发现任何一条边的左右端点至少选一个,转化成最小点覆盖就好了

代码:

/*
  Code By RyeCatcher
*/
const int maxn=2005;
const int inf=0x7ffffff;
struct Edge{
    int ne,to;
}edge[maxn<<1];
int h[maxn],num_edge=1;
inline void add_edge(int f,int to){
    edge[++num_edge].ne=h[f];
    edge[num_edge].to=to;
    h[f]=num_edge;
}
int n,k,ans=0;
int pre[maxn],match[maxn],vis[maxn];
inline void hungarian(){
    queue <int>q;
    int s,t,u,v,tmp;
    bool flag=0;
    for(ri i=1;i<=n*2;i++)pre[i]=match[i]=-1,vis[i]=0;
    for(ri o=1;o<=n;o++){
        if(match[o]!=-1)continue;
        while(q.size())q.pop();
        pre[o]=-1,q.push(o),flag=0;
        while(q.size()&&!flag){
            u=q.front();q.pop();
            for(ri i=h[u];i&&!flag;i=edge[i].ne){
                v=edge[i].to;
                if(vis[v]==o)continue;
                vis[v]=o;
                if(match[v]!=-1){
                    q.push(match[v]);
                    pre[match[v]]=u;
                }
                else{
                    flag=1;
                    s=u,t=v;
                    while(s!=-1){
                        tmp=match[s];
                        match[s]=t,match[t]=s;
                        s=pre[s],t=tmp;
                    }
                }
            }
        }
        if(match[o]!=-1)ans++;
    }
    printf("%d\n",ans);
    return ;
}
int main(){
    read(n),read(k);
    int x,y;
    for(ri i=1;i<=k;i++){
        read(x),read(y);
        add_edge(x,n+y);
        add_edge(n+y,x);
    }
    hungarian();
    return 0;
}

转载于:https://www.cnblogs.com/Rye-Catcher/p/9874146.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值