zoukankan      html  css  js  c++  java
  • 二分图


    二分图,首先第一点什么是二分图:

    把一个图的顶点划分为两个不相交集 U 和V ,使得每一条边都分别连接U、V中的顶点。如果存在这样的划分,则此图为一个二分图。比方说下图就是一个二分图。

    那么首先第一点我们要判读一个图是否为二分图,可以采用染色的思想。对于相邻的节点然不同的颜色,然后递归在这棵树上找。我们可以申明一个color数组,int color[maxn],0代表白色,1代表黑色。对于相邻的染不同的颜色,也就是color[i]=(!color[from]);那么按照我们的定义二分图点集内不能有连线,那么如果出现起点和终点的颜色相同,那么就说明这个点集内有某条连线,也就是if(ma[from][i]&&color[from]==color[i]) return 0;

    int bfs()
    {
        queue<int>q;
        q.push(1);
        color[1]=1;
        while(!q.empty())
        {
            int from=q.front();
            q.pop();
            for(int i=1;i<=n;i++)
            {
                if(color[i]==-1&&ma[from][i])
                {
                    q.push(i);
                    color[i]=(!color[from]);
                }
                if(ma[from][i]&&color[from]==color[i])
                    return 0;
            }
        }
        return 1;
    }

    第二点就是两个重要的概念:

    1.交替路:从一个未匹配点出发,依次经过非匹配边、匹配边、非匹配边…形成的路径叫交替路。

    2.增广路:从一个未匹配点出发,走交替路,如果途径另一个未匹配点,则这条交替路称为增广路。例如,图 5 中的一条增广路如图 6 所示(图中的匹配点均用红色标出):

    增广路的性质:

    1.长度len是奇数

    2.路径上第1,3,5,……,len条边是非匹配边,第2,4,6,……,len-1条边是匹配边。

    3.二分图的一组匹配 S 是最大匹配,当且仅当图中不存在增广路。


    二分图的最大匹配

    也就是使得所含匹配边数最多的匹配。

    通过数代人的努力,终于赶上了剩男剩女的大潮,假设有N个剩男,M个剩女,每个人都可能对多名异性有好感,如果一对男女互有好感,那么你就可以把这一对撮合在一起,,你拥有的大概就是下面这样一张关系图,每一条连线都表示互有好感。

    一: 先试着给1号男生找妹子,发现第一个和他相连的1号女生还名花无主,got it,连上一条蓝线

    二:接着给2号男生找妹子,发现第一个和他相连的2号女生名花无主,got it

    三:接下来是3号男生,很遗憾1号女生已经有主了,怎么办呢? 我们试着给之前1号女生匹配的男生(也就是1号男生)另外分配一个妹子。 (黄色表示这条 边被临时拆掉)

    与1号男生相连的第二个女生是2号女生,但是2号女生也有主了,怎么办呢?我们再试着给2号女生的原配()重新找个妹子(注意这个步骤和上面是一样的,这是一个递归的过程)

    此时发现2号男生还能找到3号女生,那么之前的问题迎刃而解了,回溯回去 2号男生可以找3号妹子,1号男生可以找2号妹子了,3号男生可以找1号妹子

    所以第三步最后的结果就是:

    四: 接下来是4号男生,很遗憾,按照第三步的节奏我们没法给4号男生腾出来一个妹子,我们实在是无能为力了……

    差不多就是这个很经典的动态图来帮忙理解这个算法,下面上模板代码:

    #include <iostream>
    #include <stdio.h>
    #include <string>
    #include <string.h>
    #include <algorithm>
    #include <math.h>
    #include <queue>
    #include <vector>
    using namespace std;
    typedef long long LL;
    const int INF=0x3f3f3f3f;
    const int maxn=105;
    const double eps=1e-8;
    const double pi=acos(-1.0);
    const int MOD=10056;
    int T,n,m,k,i;
    int x,y;
    int ma[maxn][maxn];//ma[i][j]=1表示xi和yj可以匹配
    int vis[maxn];
    int matchx[maxn],matchy[maxn];//matchx[i]表示在求得的最大匹配中与xi匹配的y顶点,matchy[i]同理
    int dfs(int u)
    {
        for(int i=1;i<=m;i++)
        {
            if(ma[u][i]&&!vis[i])//找到一条没有匹配的边
            {
                vis[i]=1;
                if((matchy[i]==-1)||dfs(matchy[i]))//若找到一条两端都没有匹配的边则已找到。注意前面的条件满足时将不会进行递归
                {
                    matchx[u]=i;//更新匹配,原匹配已被覆盖
                    matchy[i]=u;
                    return 1;
                }
            }
        }
        return 0;
    }
    int cal()
    {
        int ans=0;
        memset(matchy,-1,sizeof(matchy));
        memset(matchx,-1,sizeof(matchx));
        for(int i=1;i<=n;i++)
        {
            if(matchx[i]==-1)//每次找x中没有被覆盖的点
            {
                memset(vis,0,sizeof(vis));
                ans+=dfs(i);
            }
        }
        return ans;
    }
    int main()
    {
        while(scanf("%d",&n)!=EOF)
        {
            memset(ma,0,sizeof(ma));
            if(n==0)
                break;
            else
                scanf("%d %d",&m,&k);
            while(k--)
            {
                scanf("%d %d %d",&i,&x,&y);
                ma[x][y]=1;
            }
            printf("%d
    ",cal());
        }
        return 0;
    }

     

    这是无权的情况下,只要匹配的边数最大即可,那么要是有权边呢。当然它是有前提的:二分图存在完备匹配

    现在有N男N女,男生和女生每两个人之间有好感度,我们希望把他们两两配对,并且最后希望好感度和最大。

    怎么选择最优的配对方法呢? 首先,每个妹子会有一个期望值,就是与她有好感度的男生中最大的好感度。男生期望值为0。 这样,我们把每个人的期望值标出来。

    然后,开始配对。配对方法:男女两人的期望和要等于两人之间的好感度。每一轮匹配,无论是否成功,每个男生只会被尝试匹配一次!

    匹配过程:

    第一轮匹配: ============================

    女1:选择了男3(此时女1--男3) 女2:也想选择男3,男3已经在该轮匹配过了,女2无其他合适选择,匹配失败。 ===============================

    这一轮参与匹配的人有:女1,女2,男3。 怎么办?很容易想到的,这两个女生只能降低一下期望值了,降低多少呢?两个妹子都在能选择的其他人中,也就是没参与这轮匹配的男生中,选择一个期望值降低的尽可能小的人。也就是在其他人中选择一个最合适的。 比如:女1选择男1,期望值要降低1。 女2选择男1,期望值要降低1。 女2选择男2,期望值要降低2。 于是,只要期望值降低1,就有妹子可能选择其他人。所以妹子们的期望值要降低1点。 同时,刚才被抢的男生此时非常得意,因为有妹子来抢他,与是他的期望值提高了1点(就是同妹子们降低的期望值相同)。 于是期望值变成这样(当然,不参与刚才匹配过程的人期望值不变)

    第二轮匹配: ============================

    (女1已经在第一轮匹配完成了,女1--男3) 女2:选择男1。(此时女1--男3,女2--男1) 女3:选择男3,男3已经有女1了,于是女1尝试换人,换到男1,男1已经被在这一轮被尝试匹配过了。于是女1换人失败,这一轮匹配失败。 ============================

    再一次改变期望值。 这次三个女生都参与了匹配,男1和男3参与匹配。女生尝试换人,于是期望值降低1。参与匹配的男生期望值增加1。

    第三轮匹配: ============================

    上一轮女1和女2是匹配完成的。(此时女1--男3,女2--男1) 女3:选择男3,男3的当前对象女1尝试换人,换到了男1,但是男1已经有女2了,于是女2再尝试换人,换到了男2,于是女2--男2,女1--男1,女3--男3 匹配成功!!!撒花~~ ============================

    虽然不停换人的过程听起来很麻烦,但其实整个是个递归的过程,实现起来比较简单。比较复杂的部分就是期望值的改变,但是可以在递归匹配的过程中顺带求出来(这样的复杂度是O(n^3))

    觉得不好理解可以看模板代码:

    #include <iostream>
    #include <stdio.h>
    #include <string>
    #include <string.h>
    #include <algorithm>
    #include <math.h>
    #include <queue>
    #include <vector>
    using namespace std;
    typedef long long LL;
    const int INF=0x3f3f3f3f;
    const int maxn=305;
    const double eps=1e-8;
    const double pi=acos(-1.0);
    const int MOD=10056;
    int T,n,m,k;
    int visx[maxn],visy[maxn];
    int ma[maxn][maxn];
    int matchx[maxn],matchy[maxn];
    int lx[maxn],ly[maxn];//记录x集合的期望值与y集合的期望值
    int slack[maxn];//从x到y最少需要多少价值
    int dfs(int x)
    {
        visx[x]=1;
        for(int y=1;y<=m;y++)
        {
            if(visy[y])
                continue;
            int temp=lx[x]+ly[y]-ma[x][y];
            if(temp==0)
            {
                visy[y]=1;
                if((matchy[y]==-1)||dfs(matchy[y]))
                {
                    matchy[y]=x;
                    matchx[x]=y;
                    return 1;
                }
            }
            else if(slack[y]>temp)
                slack[y]=temp;//记录从x集合到y的最小值
        }
        return 0;
    }
    void KM()
    {
        memset(matchy,-1,sizeof(matchy));
        memset(ly,0,sizeof(ly));
        memset(lx,-INF,sizeof(lx));
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
            lx[i]=max(lx[i],ma[i][j]);
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=m;j++)
                slack[j]=INF;
            while(1)
            {
                memset(visx,0,sizeof(visx));
                memset(visy,0,sizeof(visy));
                if(dfs(i))
                    break;
                else
                {
                    int d=INF;
                    // 如果不能找到,就降低期望值,故求最小可降低的期望值
                    for(int j=1;j<=m;j++)
                    {
                        if(!visy[j])
                            d=min(d,slack[j]);
                    }
                    for(int j=1;j<=n;j++)
                    {
                        if(visx[j])
                            lx[j]-=d;
                    }
                    for(int j=1;j<=m;j++)
                    {
                        if(visy[j])
                            ly[j]+=d;
                        else
                            slack[j]-=d;
                    }
                }
            }
        }
    }
    int main()
    {
        while(scanf("%d",&n)!=EOF)
        {
            memset(ma,0,sizeof(ma));
            m=n;
            for(int i=1;i<=n;i++)
                for(int j=1;j<=n;j++)
                scanf("%d",&ma[i][j]);
            KM();
            int ans=0;
            for(int i=1;i<=n;i++)
                ans+=ma[i][matchx[i]];
            printf("%d
    ",ans);
        }
        return 0;
    }
    
    

    二分图经常解决的问题:

    最大匹配数:最大匹配的匹配边的数目

    求法:直接上模板求

    最小点覆盖数:选取最少的点,使任意一条边至少有一个端点被选择

    求法:最大匹配边数 = 最小点覆盖数 证明要点:     最大匹配是原二分图边集的一个子集,并且所有边都不相交,所以至少要从每条匹配边中选出一个端点,所以 最小点覆盖数>=最大匹配边数。     对任意二分图可以构造出一组点覆盖,其包含的点数等于最大匹配包含的边数。

    最大独立数:选取最多的点,使任意所选两点均不相连

    求法:最大独立数 = 顶点数 - 最大匹配数 proof:     选出最多的点构成独立集      <=>在图中去掉最少的点,使剩下的点之间没有边     <=>用最少的点覆盖所有的边     因此,去掉二分图的最小点覆盖,剩余的点就构成二分图的最大独立集,而最小点覆盖等于最大匹配数。得证。

    最小路径覆盖数:对于一个 DAG(有向无环图),选取最少条简单路径,使得每个顶点属于且仅属于一条路径。路径长可以为 0(即单个点)

    求法:最小路径覆盖数 = 顶点数 - 原DAG图的拆点二分图的最大匹配数

  • 相关阅读:
    37. Sudoku Solver(js)
    36. Valid Sudoku(js)
    35. Search Insert Position(js)
    34. Find First and Last Position of Element in Sorted Array(js)
    33. Search in Rotated Sorted Array(js)
    32. Longest Valid Parentheses(js)
    函数的柯里化
    俞敏洪:我和马云就差了8个字
    vue路由传值params和query的区别
    简述vuex的数据传递流程
  • 原文地址:https://www.cnblogs.com/jkzr/p/10036363.html
Copyright © 2011-2022 走看看