zoukankan      html  css  js  c++  java
  • 【瞎口胡】二分图

    二分图,即满足以下条件的图 (G(V,E))

    • 存在 (V) 的两个子集 (A,B) 满足 (A cup B = V)
    • (E) 中不存在边 ((u,v)) 使得 (u in A,v in B)

    直观上讲,就是这个图可以分成两列点,每一列点之间没有连边,边由一列点连向另外一列点。

    二分图判定

    任选一个点为起点开始 DFS,将起点标为黑色。按照如下策略遍历每条边:

    • 如果该边的终点没有访问过,将它的颜色标为当前点相反的颜色,并转到该点继续 DFS。
    • 如果该边的终点有颜色且和当前点相同,那么该图不为二分图。

    在图不连通时,要求每个连通块都要成功染色。

    这样,颜色相同的点在同一列,就构成了一个二分图。

    // vst ... visit
    void dfs(int i,int colors){
    	if(vst[i]&&colors!=color[i]){
    		printf("Impossible");
    		exit(0);
    		return;
    	}
    	if(vst[i])
    		return;
    	vst[i]=true;
    	color[i]=colors;
    	++(colors==0?whitesum:blacksum);
    	for(rr int j=head[i];j;j=edge[j].next){
    		dfs(edge[j].to,!colors);
    	}
    	return;
    }
    

    例题 Luogu P1330 封锁阳光大学

    二分图匹配

    在二分图中选出一些边,这些边的端点不相同,这就构成了二分图的一个匹配。边的数量就是这个匹配的大小。

    红边即为二分图的匹配,大小为 (3)。同时,这是该二分图的最大匹配。红边为该匹配的匹配边,黑边为该匹配的非匹配边。

    二分图最大匹配 · 匈牙利算法

    定义二分图的交互道路为红边与黑边交互出现的路径,且路径起点在左侧,终点在右侧。

    增广路为路径起点、终点为非匹配点的交互道路。

    观察到,将增广路上的每一条边颜色取反(红、黑)互换,匹配边增加一条,匹配的大小增加 (1)。匈牙利算法就是不断地寻找增广路,直到图中不存在增广路为止。

    当图中不存在增广路时,我们找到了该图的最大匹配。

    证明如下:

    • 有增广路的匹配一定不是最大匹配,因为可以红黑边互换颜色。
    • 没有增广路的匹配一定是最大匹配。考虑一个没有增广路的匹配 (M) 和一个最大匹配 (M')。构造图 (G=M oplus M')。令 (G) 中属于 (M) 的边为 (0) 边,属于 (M') 的边为 (1) 边,那么图中存在一些 (0,1) 交替且 (0,1) 边数量相等的交互道路。如果 (0,1) 边不等,图中存在至少一条增广路,与假设矛盾。将 (1) 边加入 (M)(0) 边从 (M) 中删除,就把 (M) 转换为了 (M')。因为 (0,1) 边数量相等,所以这样做不会使匹配的大小变化。

    综上,没有增广路是该匹配为最大匹配的充分必要条件。

    匈牙利算法的流程是:

    1. 清空所有 (vis) 标记。
    2. 找到左侧一个没有被匹配的点 (x_i)
    3. 找一条非匹配边 ((x_i,y_i)),且 (y_i) 没有 (vis) 标记。对 (y_i) 打上 (vis) 标记。
    4. 如果 (y_i) 不在匹配中,将 (x_i)(y_i) 匹配。
    5. 如果 (y_i) 在匹配中,转到与 (y_i) 匹配的点 (x_j) 并重复步骤 (2),直到找到一个没有匹配的 (y_i) 或者无路可走。如果找到了这样的 (y_i),将路径上的所有边反色,就得到了一个比原来大 (1) 的匹配。

    重复执行上面流程,直到所有 (x_i) 点作为起点执行过一次算法。此时图中不存在增广路,找到了一个最大匹配。

    为什么要对 (y_i) 打上 (vis) 标记?因为每一次进入 (y_i) 所做的操作都是相同的,如果第一次没有找到增广路,再进一次也不会找到。

    最坏情况下,每次搜索都会遍历所有点和所有边,因此时间复杂度为 (O(n^2m))

    模板 Luogu P3386 Code

    # include <bits/stdc++.h>
    
    const int N=510,M=50010;
    
    struct Edge{
    	int to,next;
    }edge[M]; 
    int head[N],sum;
    int n,m,e;
    int vis[N],clo,link[N];
    inline void add(int x,int y){
    	edge[++sum].to=y;
    	edge[sum].next=head[x];
    	head[x]=sum;
    	return;
    }
    bool dfs(int i){
    	for(int j=head[i];j;j=edge[j].next){
    		int to=edge[j].to;
    		if(vis[to]==clo)
    			continue;
    		vis[to]=clo;
    		if(!link[to]||dfs(link[to])){
    			link[to]=i;
    			return true;
    		}
    	}
    	return false;
    }
    int main(void){
    	scanf("%d%d%d",&n,&m,&e);
    	for(int i=1;i<=e;++i){
    		int u,v;
    		scanf("%d%d",&u,&v),add(u,v);
    	}
    	int ans=0;
    	for(int i=1;i<=n;++i){
    		clo=i;
    		ans+=dfs(i);
    	}
    	printf("%d",ans);
    	return 0;
    }
    

    二分图的最小点覆盖与最大独立集

    选出一个最小的点集,使得每条边至少有一个端点在这个点集中,该点集被称为二分图的最小点覆盖。

    Konig 定理:二分图的最小点覆盖 = 最大匹配(设其大小为 (k))。

    构造证明:从右侧没有匹配到的点出发 DFS,每次走非匹配边 ( o) 匹配边 ( o) 非匹配边,并将经过的点打上标记。左侧被打上标记的点和右侧没有被打上标记的点组成了二分图的最小点覆盖。

    左侧被打上标记的点和右侧没有被打上标记的点有 (k) 个。每个点都是匹配的某个端点。证明:

    • 不存在一个匹配左边、右边都没有被打上标记。
  • 相关阅读:
    2014第2周四部署环境&买火车票
    2014第2周三Web安全学习
    2014第2周二
    2014第2周一
    2013第一周日小结
    2013第1周六意外加班到很晚
    2014第一周五开发问题记URL传参乱码等
    XML文档形式&JAVA抽象类和接口的区别&拦截器过滤器区别
    通过WriteProcessMemory改写进程的内存
    【java】Windows7 下设置环境变量
  • 原文地址:https://www.cnblogs.com/liuzongxin/p/14353060.html
Copyright © 2011-2022 走看看