二分图 学习笔记

来源:互联网 发布:gitlab ci.yml java 编辑:程序博客网 时间:2024/05/18 16:56

很久之前就学过二分图,但是感觉当时理解的并不好。今天重新复习了一下二分图——for noip,在此写下一些新的体会。

二分图的定义

摘自ATP的blog——

二分图顾名思义就是可以分成两部分的图。并且这两部分内部不能有边相连。形式化地,定义图G={V,E}AG的一个子集。如果对于(x,y)E,都有xAySA或者yAxSA(总之xy不能同时属于ASA),那么称G是一个二分图。

由于二分图的特殊性,我们可以用它来搞很多事情= =

基本概念

二分图上乱七八糟的东西非常多,比如说——
匹配:在图论中,一个匹配是一个边的集合,其中任意两条边都没有公共顶点。
我们定义匹配点、匹配边、未匹配点、非匹配边,它们的含义非常显然。
最大匹配:一个图所有匹配中,所含匹配边数最多的匹配,称为这个图的最大匹配。
完美匹配:如果一个图的某个匹配中,所有的顶点都是匹配点,那么它就是一个完美匹配。显然,完美匹配一定是最大匹配(完美匹配的任何一个点都已经匹配,添加一条新的匹配边一定会与已有的匹配边冲突)。但并非每个图都存在完美匹配。
交替路:从一个未匹配点出发,依次经过非匹配边、匹配边、非匹配边…形成的路径叫交替路。
增广路:从一个未匹配点出发,走交替路,如果途径另一个未匹配点(出发的点不算),则这条交替路称为增广路。

二分图的判断

由二分图的概念我们可以得到一个非常显然的判断方法。将一个图黑白染色,要求有边直接相连的点被染成不同的颜色,如果无法染色或发生冲突,则不是二分图。
正确性显然啊▽

二分图的相关算法和结论

以下所有的结论都没有非常严谨的证明,全部是我自己yy的,可能会有一些不充分或不合理的地方叭,见笑。

最大匹配

最大匹配的概念之前已经介绍过了,那么如何求最大匹配呢?
强烈建议看这篇文章:
趣写算法系列之——匈牙利算法
不过我不是很推荐用这个blog里的代码,它每次都memset会很慢。其实vis数组可以不用bool,而是打一个标记,这样可以节省时间。

贴一个二分图判断+最大匹配的模板

#include<iostream>#include<cstring>#include<cstdio>using namespace std;#define N 40005int n,m,x,y,ans;bool flag;int tot,point[N],nxt[N*2],v[N*2];int belong[N],vis[N],col[N];void clear(){    ans=0;    tot=0;memset(point,0,sizeof(point));memset(v,0,sizeof(v));memset(nxt,0,sizeof(nxt));    memset(belong,0,sizeof(belong));memset(vis,0,sizeof(vis));memset(col,-1,sizeof(col));}void add(int x,int y){    ++tot; nxt[tot]=point[x]; point[x]=tot; v[tot]=y;}void dfs(int x,int fa){    for (int i=point[x];i&&flag;i=nxt[i])        if (v[i]!=fa)        {            if (col[v[i]]==-1)            {                col[v[i]]=col[x]^1;                dfs(v[i],x);            }            else            {                if (col[v[i]]!=(col[x]^1)) flag=false;                return;            }        }}bool find(int x,int k){    for (int i=point[x];i;i=nxt[i])        if (vis[v[i]]!=k)        {            vis[v[i]]=k;            if (!belong[v[i]]||find(belong[v[i]],k))            {                belong[v[i]]=x;                return true;            }        }    return false;}int main(){    while (~scanf("%d%d",&n,&m))    {        clear();        if (n==1)        {            puts("No");            continue;        }        for (int i=1;i<=m;++i)        {            scanf("%d%d",&x,&y);            add(x,y);        }        flag=true;        col[1]=0;dfs(1,0);        if (!flag)        {            puts("No");            continue;        }        for (int i=1;i<=n;++i)            if (find(i,i)) ans++;        printf("%d\n",ans);    }}

个人的一点点理解:
实际上匈牙利算法的每一次增广只是在不断地调整匹配边而已,直到不能匹配为止。

你会发现,最大匹配非常好用。二分图的很多问题都能转化为这个基本模型。

最小路径覆盖

一个有向无环图(不一定是二分图),要求用尽量少的不相交的简单路径覆盖所有的节点。
建图方法:将原图每个点拆为两个点,对于每一条边(u,v),从相应的第一个点向第二个点连有向边,形成一个二分图。
一个结论:最小路径覆盖=顶点数-最大匹配
注意上面的顶点数是指原图的顶点数。
证明:
假设每一个点的连出点成为出点,连入点成为入点。那么我们可以从每个点的入点向出点连一条有向边。这条边可以称之为辅助边,它的作用是将拆开的点重新联系起来。那么我们从每一个未访问过的点的入点出发,依次经过辅助边、匹配边、辅助边、匹配边直到无路可走为止,这就画出了一条一条的路径覆盖。可见,如果刚开始每一个点对答案的贡献为1的话,每多一个匹配答案就会减少1。就可以证明上面那个结论了。

最大独立集

寻找一个最大点集A,满足u,vV,(u,v)E
二分图的最大独立集=顶点数-最大匹配
证明:
看到这个公式,我们可以形象地将其理解为先选出所有没有匹配的点,然后再在每一对匹配点对中选出一个点来。可以证明这样做是显然正确并且唯一成立的。
①因为两两无边,所以每一对匹配只能选一个点,并且必须是最大匹配。
②一定存在一种方案,使得从每一个匹配中选出一个点,使其和其它点没有边。可以用反证法。
1°假设存在两个匹配,从一个匹配中选一个点,不管怎样选,这两个点之间一定有边。那么可以发现这四个点是一个完全图而不是二分图,与原命题矛盾。
2°假设存在一个匹配,不管选哪个点,都和其它未匹配点有边。那么假设匹配边(u,v)u,v均为未匹配点,选uuu有边,选vvv有边,那么显然砍断当前的匹配边(u,v),使(u,u)(v,v)成为匹配边是更大的匹配,与原名题矛盾。

综上,命题得证。

最大团

寻找一个最大点集,任意两点间有边。
二分图的最大团=其补图的最大独立集
补图就是把二分图中两集合相互有边的点全部变为无边,相互无边的点全部变为有边。显然二分图的补图还是二分图。
这个结论非常显然啊,图也是互补的条件也是互补的,所以一定是等价的啊= =

最小顶点覆盖

寻找一个最小点集A,满足对于(u,v)E,(uA)(vA)
最小顶点覆盖=最大匹配
证明:
①充分性:已知一个最小顶点覆盖,推出最大匹配。
1°如果这个点集是最小顶点覆盖,那么对于点集中一定不存在u,满足对于(u,v)E,vA。所以在这个点集中,对于u选一条边(u,v)作为匹配边,满足vA,一定是一个合法的匹配。
2°用反证法:假设这个匹配不是最大匹配,那么一定存在边(u,v)满足(uA)(vA),那么这不是一个最小顶点覆盖,与已知矛盾。

②必要性:已知一个最大匹配,推出最小顶点覆盖。
1°因为是最大匹配,那么未匹配边里一定不存在一条两个端点都未匹配的边,那么就从每一对匹配中选出一个点,一定存在一种方案使其是一个合法的顶点覆盖。
2°同样用反证法,如果这个点集不是最小的,那么一定存在一条边的两个端点都在最大匹配里,与已知矛盾。
至此,可以证明由最大匹配能推出最小顶点覆盖。

证毕。

感觉二分图的相关算法还有很多啊,比如什么稳定婚姻问题balabala,以后还要继续学,持续更新。

0 0
原创粉丝点击