zoukankan      html  css  js  c++  java
  • 二分图学习笔记

    预备知识

    • 二分图:如果一张无向图((V,E))存在点集(A,B),满足(|A|,|B|≥1)(A∩B=empty)(A∪B=V),且对于(x,y∈A)(x,y∈B)((x,y) otin E),则称这张无向图为二分图(A,B)分别为二分图的左部右部
    • 图的匹配:对于一张无向图((V,E)),若存在一个边集(E'),满足(E'sube E),且对于任意(p,qin E')(p,q)没有公共端点,则称(E')为这张无向图的一组匹配
      • 匹配边/非匹配边:对于任意一组匹配(S),属于(S)的边称为匹配边,不属于(S)的边称为非匹配边
      • 匹配点/非匹配点:匹配边的端点称为匹配点,其他点称为非匹配点
      • 匹配的增广路:如果二分图中存在一条连接两个非匹配点的路径(path),使得非匹配边与匹配边在(path)上交替出现,则称(path)是匹配(S)增广路
        • 性质1:长度为奇数。
        • 性质2:奇数边是非匹配边,偶数边是匹配边。
        • 性质3:如果把路径上所有边的状态(是否为匹配边)取反,那么得到的新的边集(S')仍是一组匹配,并且匹配的边数增加了1.
    • 最大匹配:在二分图中,包含边数最多的一组匹配。
    • 完备匹配:在二分图(((A,B),E))中,设最大匹配为(E'),且有(|A|=|B|=|E'|),则称二分图有完备匹配。
    • 最优匹配:对于一张边有边权的二分图,所有最大匹配中边权总和最大的,称为最优匹配

    二分图判定

    判定定理

    一个无向图是二分图,当且仅当图中不存在奇环。

    核心流程

    一般应用染色法即可。

    1. 将所有节点初始化为未染色。
    2. 从一个未染色的节点(u)开始,染色为(c)。(如果(u)没有前驱,则(c)(0)(1)均可)
    3. (u)出发遍历所有与其连接的节点(v),如果:
      • (v)未染色,设(c)为节点(u)的相反色,跳转至第2步;
      • (v)颜色与(u)相同,则该图不是二分图;
      • (v)颜色是(u)的相反色,继续;

    可以看出其实是一个DFS的过程。如果给定图是连通图,则一次DFS即可;否则需要遍历每一个点,每次从未染色的点开始DFS。

    模板

    bool dfs(int u, int c) {
    	col[u] = c;
    	for (int i = head[u]; i; i = e[i].next) {
    		int v = e[i].to;
    		if (col[v] == col[u]) return false;
    		if (col[v] == -1 && !dfs(v, !col[u])) return false;
    	}
    	return true;
    }
    

    二分图匹配

    二分图最大匹配

    判定定理

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

    匈牙利算法

    1. 初始时设匹配(S)为空集,即所有边都是非匹配边。
    2. 枚举二分图左部上的点(x),给(x)寻找与其相连的右部点(y)尝试匹配,当满足下列条件之一时,匹配成功(找到增广路):
      • (y)为非匹配点
      • (y)已与(x')匹配,但从(x')出发能找到另一个(y')与之匹配
    3. 重复第2步,直到找不到增广路。

    时间复杂度(O(p imes e+q)),其中(p)为左部点数量,(q)为右部点数量,(e)为图的边数。

    一个小小的优化:当左部点数量明显大于右部点数量时,改为枚举右部点。

    (Dinic)跑最大流的时间复杂度是(O(nsqrt e)),其中(n)为节点总数,(e)为图的边数。

    模板

    bool vis[maxn];
    int res[maxn];
    //vis[i]记录节点i在试图改变匹配对象时成功与否
    //res[i]记录节点i的匹配对象
    
    bool match(int x) {
    	//注意,参数x都是左部点
    	for (int i = head[x]; i; i = e[i].next) {
    		int y = e[i].to;
    		if (!vis[y]) {
    			//对于左部点x而言,右部点y还没有试图“腾出来”过
    			vis[y] = true;
    			//尝试了就只有两种结果:y要么最终配上了x,要么实在腾不出来
    			//无论是哪一种,y都没必要再次尝试“腾出来”了,所以只试一次就行
    			if (!res[y] || match(res[y])) {
    				res[x] = y;
    				res[y] = x;
    				//这里默认左部点和右部点的编号没有重复的
    				return true;
    			}
    			//y还没有匹配过,或y的匹配对象x'可以找到新的匹配对象
    			//则本次x与y的匹配成功
    		}
    	}
    	return false;
    }
    
    int main() {
      ...
    	int ans = 0;
    	for (int i = 1; i <= p; i++) {
    		memset(vis, false, sizeof(vis));
    		//对于枚举的每一个左部点,右部点的状态都是还没尝试过
    		if (match(i)) ans++;
    	}
      ...
    }
    

    二分图最优匹配

    对于一张边有边权的二分图,所有最大匹配中边权总和最大的,称为最优匹配

    KM算法

    1. 对于每一个左部点,以与它相连的所有边中的最大边权,给它赋一个期望值。对于每一个右部点,期望值设为0.
    2. 枚举每一个左部点,开始匹配。原则:只选择边权与左右部点期望值之和相同的边。
    3. 如果匹配成功,继续枚举。如果找不到符合要求的未匹配的边,那么尝试让符合要求的已匹配的边的右部点改换匹配对象。这一步与匈牙利算法大致一样,可以看作是寻找增广路(path)
      • (path)上的所有左部点期望值(-z)(path)上的所有右部点期望值(+z).其中(z)为能使左部点找到新边的最小改变量。
      • 重复第3步。

    注意:在匈牙利算法中,(vis)数组是用于记录右部点(y)是否已尝试过;而在KM算法中,(vis)数组既有前述功能,也标记了节点(i)是否在增广路(path)上,以便期望值的增减。所以每枚举一个左部点,都要令vis[x]=true.

    模板

    int gap;
    //记录能使节点配对成功的最小改变量
    bool match(int x) {
    	vis[x] = true;
      //别漏了这一步
    	for (int i = head[x]; i; i = e[i].next) {
    		int y = e[i].to;
    		if (!vis[y]) {
    			int tmp = val[x] + val[y] - e[i].dis;
    			if (tmp == 0) {
    				vis[y] = true;
    				if (!res[y] || match(res[y])) {
    					res[x] = y;
    					res[y] = x;
    					return true;
    				}
    			}
    			else if (tmp > 0) {
    				gap = min(gap, tmp);
    			}
    		}
    	}
    	return false;
    }
    
    void km() {
    	for (int i = 1; i <= n; i++) {
    		while (1) {
    			gap = INF;
    			memset(vis, false, sizeof(vis));
    			if (match(i)) break;
    			//找不到符合要求的边,降低期望,重新尝试匹配
    			for (int i = 1; i <= p; i++) {
    				if (vis[i]) val_x[i] -= gap;
    			}
          //左部点降低期望
    			for (int i = 1; i <= q; i++) {
    				if (vis[i]) val_y[i] += gap;
    			}
          //右部点提高期望
    		}
    	}
    }
    

    二分图最小点权覆盖集

    定义

    • 图的点覆盖:对于无向图((V,E)),若存在一个点集(V'sube V),对于任意(ein E)(e)至少有一个端点属于(V'),则称(V')为图的一组点覆盖
    • 二分图最小点权覆盖:在二分图中,包含点最少/点权最小的一组点覆盖为最小点权覆盖。

    定理

    最小点覆盖(所包含的点数)(=) 最大匹配(包含的边数)

    二分图最大独立集

    定义

    • 图的独立集:对于无向图((V,E)),若存在一个点集(V'),满足(V'sube V),且对于任意(p,qin V'),边((p,q) otin E),则称(V')为这张图的独立集。
    • 图的最大独立集:包含点数最多的独立集。
    • 图的团:对于无向图((V,E)),若存在一个点集(V'),满足(V'sube V),且对于任意(p,qin V'),边((p,q)in E),则称(V')为这张图的一组团。
    • 图的最大团:包含点数最多的团。

    定理

    对于一张(n)个节点的二分图,设其最大独立集为(V'),最小点覆盖集为(A'),最大匹配为(B'),有(|V'|=n-|A'|=n-|B'|)

    DAG最小路径覆盖

    定义

    • 最小不相交路径覆盖:能覆盖所有节点且互不相交的路径的最少数量。
    • 最小可相交路径覆盖:能覆盖所有节点且可以相交的路径的最少数量。
    • 拆点二分图:设DAG的节点总数为(n),将每个节点拆成编号为(x)(x+n)的两个点。建立一张新的二分图,其中编号(1)$n$的节点为左部,编号$n+1$(2n)的节点为右部。对于原图的每条有向边((x,y)),在二分图的左部点(x)与右部点(y+n)之间连边。最后得到的二分图为原图的拆点二分图。

    定理

    DAG的最小不相交路径覆盖=原图的节点数 - 新图的最大匹配

    DAG的最小可相交路径覆盖的求法:用(Floyd)对原图求传递闭包,即可转化为求最小不相交路径覆盖。

    模型要素

    二分图匹配模型

    • 点能分成内部没有边的两个集合
    • 每个点只能与1条匹配边相连

    二分图最小点覆盖模型

    • 每条边有两个端点,二者至少选择一个
  • 相关阅读:
    时间计算
    DateTime
    C# trim split dataGrid
    something
    生活
    如何导入外部的源码到eclipse中
    java类中获取ServletContext的方法
    获取spring容器上下文(webApplicationContext)的几种方法
    java反射(转)
    mysql常见命令
  • 原文地址:https://www.cnblogs.com/streamazure/p/13778319.html
Copyright © 2011-2022 走看看