不严格说明什么是二分图 ==> 能将图的顶点分为两个集合、集合中的顶点没有边相连
匹配 ==> 在图G中两两没有公共端点的边集合M ? 边集合E
除了匹配顺便说一些其他的术语
边覆盖 ==> 在图G中任意顶点都至少是F中某条边的端点的边集合F ? E
独立集 ==> 在G中两两互不相连的顶点集合S ? V
顶点覆盖 ==> 在G中任意边都至少一个端点属于S的顶点集合 S ? V
即 匹配 && 边覆盖 属于边的集合、独立集 && 顶点覆盖 属于顶点的集合
其中有一些这样子的关系
V = 最大匹配 + 最小边覆盖 = 最大独立集合 + 最小顶点覆盖
其中最大独立集合和最小顶点覆盖在非二分图下都是 NP-Hard 问题
而在二分图下有 ==> 最大匹配 = 最小顶点覆盖
这一性质使得需要求顶点覆盖的题目在分析出是二分图后可以转化为匹配解决
求二分图匹配的算法有
① 转化为最大流
将原图中的所有无向边改成有向边,方向从顶点集合U到顶点集合V,定义边容量为 1
然后建立一个超级源点连接容量为 1 的边到 U 的各个顶点
同样建立超级汇点并从 V 的各个顶点连接容量为 1 的边到超级汇点
最后求出此图的最大流就是最大匹配了,算法复杂度为 O(V*E)
②匈牙利算法( KM算法 )
匈牙利算法是利用一个结论
增广路中的匹配边永远比未匹配边多一条
所以可以不断在二分图中去寻找增广路,然后来交换匹配和未匹配边的关系
来达到“蹭出”空间的办法,最后加大匹配数
这里直接给出模板,用邻接表实现的复杂度为 O(V*E)
#include<stdio.h> #include<string.h> using namespace std; const int maxn = 1e3 + 10; const int maxm = 1e4 + 10; struct EDGE{ int v, nxt; }Edge[maxm]; int Head[maxn], cnt; int N, M; int match[maxn]; bool used[maxn]; inline void init() { for(int i=0; i<=N; i++) Head[i] = -1; cnt = 0; } inline void AddEdge(int from, int to) { Edge[cnt].v = to; Edge[cnt].nxt = Head[from]; Head[from] = cnt++; } bool DFS(int v) { used[v] = true; for(int i=Head[v]; i!=-1; i=Edge[i].nxt){ int Eiv = Edge[i].v; int w = match[Eiv]; if(w < 0 || !used[w] && DFS(w)){ match[v] = Eiv; match[Eiv] = v; return true; } } return false; } int Bipartite_Matching() { int ret = 0; memset(match, -1, sizeof(match)); for(int i=0; i<N; i++) if(match[i] < 0){ memset(used, false, sizeof(used)); if(DFS(i)) ret++; } return ret; }