二分图判定,二分图匹配(匈牙利算法),多重匹配,最小顶点覆盖,带权二分图匹配(KM算法)

本文介绍了二分图的判定方法,通过递归染色判断是否存在矛盾。接着讲解了最大匹配的匈牙利算法,并通过实例展示了匹配过程。讨论了最小点覆盖问题,指出最小点覆盖数等于最大匹配数。此外,还提到了二分图的多重匹配和带权二分图最大匹配的概念,提供了相关资源进行深入学习。

目录

 

二分图判定:

最大匹配

最小点覆盖

二分图多重匹配

带权二分图最大匹配


二分图判定:

两组顶点,每组顶点内没有相交。

如何判断二分图?

递归,染色。

随意将一顶点染色为1,将与其相连的染色为-1,再将与-1相连的点染为1,如此重复直到遍历完整个图

如果有矛盾(两个相连点染色相同)则不为二分图

代码

int dfs(int u,int sum)
{
    f[u]=sum;
    for(int i=0; i<e[u].size(); i++)  //vector存图(无向)
    {
        int v=e[u][i];
        if(f[v]==sum)  //颜色相同
            return 1;
        if(!f[v]&&dfs(v,-sum))  //深搜路径上有颜色相同
            return 1;
    }
    return 0;
}

二分图常用建图方法 

 

最大匹配

匈牙利算法  O(ne)

如图,这是一个二分图,左边集合为女生,右边为男生,图中连线为所有可能的找对象方案,如何选择使得有更多的女生可以找到男朋友(每个女/男生只能有一个男/女朋友)

首先从第一个女生开始,她只有一个心仪的对象,男2.那就相爱吧!

第二个女生, 心仪对象有两个,两个都没有女朋友,那就先选男1吧!

然后是女3,她心仪的男2已经有女1了呀!我们来调解下,看看女1可不可以换个男朋友,把男2让出来(女1:为啥要让我让!!)

哦吼,很不巧,女1死心塌地只要男2一个(调解失败)

那我们再来女3这边调解把,发现女3还喜欢男4,还说什么?上啊!

 女4,喜欢的男1也有对象,那我们来调解下:

先来看看男1的女朋友女2怎么想(女2:随便咯,我还有一个心仪对象呢),然后女2换了个男朋友——男3(渣女的既视感怎么回事。。)

女4也就可以选择男1当男朋友啦!

最后是女5,喜欢的男3有了对象(调解)

男3对象是女2,女2的另一个心仪对象男1也有了女朋友(再次调解)

男1 对象是女4,女4没有别的心仪对象(调解失败)

这下女2也没有别的对象可以换(调解失败)

调解女5,她的另一个心仪对象男5没有对象,匹配成功!

以上所有调解过程都是递归过程 

实现极其简单,(递归查询)这里只贴代码

//二位数组存图,遍历左边的一组顶点,遍历左边每个顶点与右边顶点连线情况
int Dfs(int u)   
{
    for(int i=0; i<e[u].size(); i++)  
    {
        int v=e[u][i];
        if(!book[v])  //如果本次没有遍历到,防止死循环
        {
            book[v]=1;
            if(!lever[v]||Dfs(lever[v]))  //如果右边没有连线或者已经连线但是可以换为其他线的
            {
                lever[v]=u;    //将右边点v与左边点u相连
                return 1;   //成功
            }
        }
    }
    return 0;
}
int pi()
{
    int sum=0;
    memset(lever,0,sizeof(lever));
    for(int i=1; i<=n; i++)  
    {
        memset(book,0,sizeof(book));  //每次清零
        if(Dfs(i))
            sum++;
    }
    return sum;
}

hk算法(时间复杂度  O(sqrt(n)*e)  )

多了一个bfs判断增广路

#include<stdio.h>
#include<algorithm>
#include<string.h>
#include<math.h>
#include<queue>
using namespace std;
#define inf 0x3f3f3f3f
#define ll long long
const int maxn=3010;
int book[maxn],lever[maxn],vis[maxn][maxn];
ll x[maxn],y[maxn],z[maxn],x2[maxn],y2[maxn];
int n,m,dis;
int nl[maxn],nr[maxn],dl[maxn],dr[maxn];
int judge()
{
    queue<int>q;
    memset(dl,-1,sizeof(dl));
    memset(dr,-1,sizeof(dr));
    dis=inf;
    for(int i=1; i<=n; i++)
    {
        if(nl[i]==-1)
        {
            q.push(i);
            dl[i]=0;
        }
    }
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        if(dl[u]>dis) break;
        for(int i=1; i<=m; i++)
        {
            if(vis[u][i]&&dr[i]==-1)
            {
//                printf("%d %d %d\n",u,i,nr[i]);
                dr[i]=dl[u]+1;
                if(nr[i]==-1)
                    dis=dr[i];
                else
                {
                    dl[nr[i]]=dr[i]+1;
                    q.push(nr[i]);
                }
            }
        }
    }
//    printf("%d@\n",dis);
    return dis!=inf;
}
bool Dfs(int u)
{
    for(int i=1; i<=m; i++)
    {
        if(!book[i]&&vis[u][i]&&dr[i]==dl[u]+1)
        {
            book[i]=1;
            if(nr[i]!=-1&&dr[i]==dis) continue;
            if(nr[i]==-1||Dfs(nr[i]))
            {
//                 printf("****%d %d\n",i,nr[i]);
                nr[i]=u;
                nl[u]=i;
                return true;
            }
        }
    }
    return false;
}
int pi()
{
    int sum=0;
    memset(nl,-1,sizeof(nl));
    memset(nr,-1,sizeof(nr));
    while(judge())
    {
        for(int i=1; i<=n; i++)
        {
            memset(book,0,sizeof(book));
            if(nl[i]==-1&&Dfs(i))
            {
                sum++;
//                printf("%d\n",sum);
            }
        }
    }
    return sum;
}
int main()
{
    int t,op=0;
    scanf("%d",&t);
    while(t--)
    {
        ll time;
        scanf("%lld",&time);
        scanf("%d",&n);
        memset(vis,0,sizeof(vis));
        for(int i=1; i<=n; i++)
            scanf("%lld %lld %lld",&x[i],&y[i],&z[i]);
        scanf("%d",&m);
        for(int i=1; i<=m; i++)
            scanf("%lld %lld",&x2[i],&y2[i]);
        for(int i=1; i<=n; i++)
        {
            for(int j=1; j<=m; j++)
            {
//                printf("%lf\n",dis);
                if(time*time*z[i]*z[i]>=(x[i]-x2[j])*(x[i]-x2[j])+(y[i]-y2[j])*(y[i]-y2[j]))
                {
                    vis[i][j]=1;
//                    printf("%d %d\n",i,j);
                }
            }
        }
        printf("Scenario #%d:\n",++op);
        printf("%d\n",pi());
            printf("\n");
    }
    return 0;
}

最小点覆盖

最小点覆盖就是用最少的点,连接完所有顶点。

如图,这是最大匹配 

右边集合1没有匹配到,以他为起点找到 未匹配边->匹配边->未匹配边->...->匹配边(以匹配边为结尾)这样的一条增广路如图: 

最小点覆盖的个数就是(蓝色线)左边匹配过的点和右边没有匹配过的点的集合,也就是左边2和右边2 3 5.一共有4点

真巧,最小点覆盖数=最大匹配数

所以求最小点覆盖数量时直接套用二分图匹配模板即可。

二分图多重匹配

  与二分图匹配不同的是右边集合可匹配次数不全为1.

 有些题目限定匹配次数,有些题目不限次数

解决这类问题的方法就是开一个结构体数组,标记右边每个点已经匹配的次数和他们分别和哪些点匹配即可

代码:

#include<stdio.h>
#include<algorithm>
#include<string.h>
#include<math.h>
#include<queue>
using namespace std;
#define inf 0x3f3f3f3f
#define ll long long
const int maxn=2000;
struct node
{
    int num,x[maxn];   //num记录次数,x数组记录分别匹配的点是哪个
} lever[maxn];
int book[maxn],vis[maxn][maxn],biao[maxn];
char a[100];
int n,m,k;
int dfs(int u,int k)
{
    for(int i=0; i<m; i++)
    {
        if(vis[u][i]&&!book[i])  //没有遍历过该点并且有连线
        {
            book[i]=1;
            if(lever[i].num<k)    //判断匹配次数没有超过(当前点可以匹配)
            {
                lever[i].x[lever[i].num++]=u;  //匹配成功
                return 1;
            }
            else   //匹配次数超过(不可匹配)
            {
                for(int j=0; j<lever[i].num; j++)  //调解
                {
                    if(dfs(lever[i].x[j],k))   //调解成功
                    {
                        lever[i].x[j]=u;    //将他的匹配点改变
                        return 1;
                    }
                }
            }
        }
    }
    return 0;
}
int pi(int k)
{
    int sum=0;
    for(int i=1; i<=n; i++)
    {
        memset(book,0,sizeof(book));   //每个点只遍历右边每个点一次。
        if(dfs(i,k))
        {
            sum++;
        }
    }
    return sum;
}
int main()
{
    while(~scanf("%d %d",&n,&m)&&(n||m))
    {
        int x,y;
        memset(vis,0,sizeof(vis));
        for(int i=1; i<=n; i++)
        {
            char ch;
            while(1)
            {
                scanf("%d%c",&y,&ch);
                vis[i][y]=1;
                if(ch=='\n')
                    break;
            }
        }
        int maxx=0,l=1,r=n,mid;
       while(l<=r)     //这里的匹配次数是二分决定的(因为题目要求最小和最大匹配次数差最小)
       {
           memset(lever,0,sizeof(lever));
           mid=(l+r)/2;
           if(pi(mid)>=n)
           {
               maxx=mid;
               r=mid-1;
           }
           else
            l=mid+1;
       }
        printf("%d\n",maxx);
    }

    return 0;
}

带权二分图最大匹配

https://blog.csdn.net/weixin_43093481/article/details/84558029

可以看这个博客学习。过程有些麻烦我懒得画了。。。

就是失配的时候左边涉及到的点的值减少,右边涉及到点的值增加。

#include<stdio.h>
#include<algorithm>
#include<string.h>
#include<math.h>
#include<queue>
using namespace std;
#define inf 0x3f3f3f3f
#define ll long long
const int maxn=310;
int h[maxn][maxn],visgril[maxn],visboy[maxn];  //记录每条线的权值,标记左边点,标记右边点
int left[maxn],right[maxn];  //左边点权值,右边点权值
int lever[maxn],slack[maxn];  //记录匹配关系,记录差值。
int nx,ny;
int dfs(int u)
{
    visgril[u]=1;    //标记左边有关点
    for(int i=1; i<=ny; i++)
    {
        if(visboy[i]) continue;
        if(h[u][i]==left[u]+right[i])   //匹配条件
        {
            visboy[i]=1;   //标记右边有关点
            if(!lever[i]||dfs(lever[i]))
            {
                lever[i]=u;
                return 1;
            }
        }
        else     //失配,计算左边有关点和右边无关点与匹配条件的差值
            slack[i]=min(left[u]+right[i]-h[u][i],slack[i]);
    }
    return 0;
}
int km()
{
    memset(lever,0,sizeof(lever));
    memset(right,0,sizeof(right));  //右边赋值为0;
    for(int i=1; i<=nx; i++)
    {
        memset(slack,inf,sizeof(slack));   //差值无穷大
        while(1)
        {
            memset(visgril,0,sizeof(visgril));   //清空标记
            memset(visboy,0,sizeof(visboy));
            if(dfs(i))    //如果匹配成功跳出
                break;
            int d=inf;
            for(int j=1; j<=ny; j++)   //找到记录的差值的最小值d
            {
                if(!visboy[j])
                    d=min(slack[j],d);
            }
            for(int j=1; j<=nx; j++)
            {
                if(visgril[j])   //左边有关点值减少d
                    left[j]-=d;

            }
            for(int j=1; j<=ny; j++)
            {
                if(visboy[j])  //右边有关点值增加d
                    right[j]+=d;
                else   //右边无关点的差值减小d
                    slack[j]-=d;
            }
        }
    }
    int sum=0;
    for(int i=1; i<=ny; i++)
        sum+=h[lever[i]][i];   //计算权值
    return sum;
}
int main()
{
    int n;
    while(~scanf("%d",&n))
    {
        nx=ny=n;   //左边点范围和右边点范围
        memset(left,-inf,sizeof(left));
        for(int i=1; i<=n; i++)
        {
            for(int j=1; j<=n; j++)
            {
                scanf("%d",&h[i][j]);
                left[i]=max(h[i][j],left[i]);  //左边赋值为最大
            }
        }
        printf("%d\n",km());
    }
    return 0;
}

带权二分图最小匹配只需要将权值 h [ i ] [ j ] 变为 - h[ i ] [ j ]

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值