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图的拆点二分图的最大匹配数

  • 相关阅读:
    Python基础之迭代器、生成器
    Python基础之模块+异常
    Python基础之面向对象思维解决游戏《天龙八部》
    Oracle创建存储过程
    数据库范式
    Oracle条件判断
    Oracle的三种循环
    Oracle的pl/sql变量类型
    oracle如何实现去重和分页
    相关子查询和非相关子查询
  • 原文地址:https://www.cnblogs.com/jkzr/p/10036363.html
Copyright © 2011-2022 走看看