二分图 简介 二分图又称作二部图,是图论中的一种特殊模型。 设 G = (V, E) 是一个无向图,如果:
顶点 V 可分割为两个互不相交的子集 (A, B)
图中的每条边 (i, j) 所关联的两个顶点 i 和 j 分别属于这两个不同的顶点集 $(i \ \in \ A, \ j \ \in \ B)$
则称图 G 为一个二分图。
简单来说,把一个图的顶点划分为两个不相交子集 ,使得每一条边都分别连接两个集合中的顶点。如果存在这样的划分,则此图为一个二分图。
性质
如果两个集合中的点分别染成黑色和白色,可以发现二分图中的每一条边都一定是连接一个黑色点和一个白色点。(染色法)
二分图不存在长度为奇数的环(即每个环的长度必为偶数):因为每一条边都是从一个集合走到另一个集合,只有走偶数次才可能回到同一个集合。
判定:染色法 思路 根据二分图只有偶环的性质,我们可以使用 DFS 或者 BFS 来遍历这张图。如果发现了奇环,那么就不是二分图,否则是。
任意选取一个点,将其颜色染为红色,将与其相邻的节点染为绿色,接着把与绿色相邻的节点染为红色……直到出现两个相邻的节点为同一颜色:不是二分图;或成功将所有节点染色:是二分图。
模板 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 #define _CRTSECURE_nOWARnInGS #pragma warning (disable:4996) #include <cstdio> #include <cstring> #include <string> #include <cmath> #include <cctype> #include <algorithm> #include <iostream> #include <queue> #include <stack> #include <vector> #include <map> #include <set> #include <list> using namespace std;const int MAXN = 100015 , MAXM = 500015 ;int T, n, m; int cnt;int front[MAXM];struct EDGE { int to, next, w; }; EDGE edge[MAXM]; void Init () { memset (front, 0 , sizeof (front)); cnt = 0 ; } void add_edge (int u, int v, int w) { ++cnt; edge[cnt].to = v; edge[cnt].w = w; edge[cnt].next = front[u]; front[u] = cnt; } int color[MAXN]; bool bipartite (int u) { for (int i = front[u]; i; i = edge[i].next) { int v = edge[i].to; if (color[v] == color[u]) return 0 ; if (!color[v]) { color[v] = 3 - color[u]; if (!bipartite (v)) return 0 ; } } return 1 ; } int main () { Init (); scanf ("%d%d" , &n, &m); for (int i = 1 ; i <= m; i++) { int u, v; scanf ("%d%d" , &u, &v); add_edge (u, v, 1 ); add_edge (v, u, 1 ); } memset (color, 0 , sizeof (color)); color[1 ] = 1 ; if (bipartite (1 )) printf ("YES\n" ); else printf ("NO\n" ); return 0 ; }
相关概念 匹配 在给定一个二分图 G,在 G 的一个子图 M 中,若 M 的边集中的任意两条边都不依附于同一个顶点,则称 M 是一个匹配。
简单来说,匹配就是一个二分图中边的集合,其中任意两条边都没有公共顶点。
上图中红色边连接的 1 - 5 和 4 - 7 就是两个匹配。
最大匹配 给定二分图 G 中的所有匹配,所含匹配边数最多的匹配,称为这个图的最大匹配,选择最大匹配的问题即为图的最大匹配问题
如图,红边就是一个最大匹配
即:在不存在一个顶点连接多条边的情况下,连接尽量多的边
完全匹配 一个匹配中,图中每个顶点都与图中某条边相关联,则称此匹配为完全匹配,即一个图的某个匹配中,所有的顶点都是匹配点,就是一个完全匹配。
显然,由于完全匹配的任何一个点都已经匹配,添加一条新的匹配边一定会与已有的匹配边冲突,因此完全匹配一定是最大匹配。但要注意的是,并非每个图都存在完全匹配。
简单来说,对于一个二分图,左右点集数量不一定相同,左点集中的每一个点都与右点集的一个点匹配,或者右点集中的每一个点都与左点集的一个点匹配。(两个点集其中之一满足一对一即可)
完美匹配 对于一个二分图,左点集与右点集的点数相同,若存在一个匹配,包含左点集、右点集的所有顶点,则称为完美匹配。
简单来说,对于一个二分图,左右点集数量相同,左点集中的每一个点都与右点集的一个点匹配,并且右点集中的每一个点都与左点集的一个点匹配。(两个点集都要满足一对一)
应用 无权重最大匹配:匈牙利算法 模板 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 #define _CRTSECURE_NOWARNINGS #pragma warning (disable:4996) #include <cstdio> #include <cstring> #include <string> #include <cmath> #include <cctype> #include <algorithm> #include <iostream> #include <queue> #include <stack> #include <vector> #include <map> #include <set> #include <list> using namespace std;const int MAXN = 301 ;int p, n; int ans; int mp[MAXN][MAXN]; int vis[MAXN], a[MAXN];bool dfs (int x) { for (int i = 1 ; i <= n; i++) { if (!vis[i] && mp[x][i]) { vis[i] = 1 ; if (a[i] == 0 || dfs (a[i])) { a[i] = x; return 1 ; } } } return 0 ; } int main () { ans = 0 ; scanf ("%d%d" , &p, &n); memset (a, 0 , sizeof (a)); memset (mp, 0 , sizeof (mp)); for (int i = 1 ; i <= p; i++) { memset (vis, 0 , sizeof (vis)); if (dfs (i)) ans++; } printf ("%d" , ans); return 0 ; }
最小点覆盖数 在一个二分图中,每次选一个点覆盖住他所在的一行或者一列,选取最少的点可以把所有的边覆盖, 点的最少个数就是最小点覆盖。
在关系矩阵中,一次将一行/一列的 1 变为 0,求最少变化次数将所有 1 变为 0。
最大独立集
最大独立集 = |V| - 最大匹配数
独立集:一个点集,点集中的各点没有关系。
最大独立集:点的个数最多的独立集。
最小边覆盖
最小边覆盖 = 最大独立集 = n - 最大匹配
边覆盖集:就是 G 中所有的顶点都是 E* 中某条边的邻接顶点(边覆盖顶点),一条边只能覆盖 2 个顶点。
最小边覆盖就是边数最小的边覆盖集中的边数
最大带权匹配:KM 算法 时间复杂度 · $O(n^{3})$ 模板 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 template <typename T>struct hungarian { int n; vector<int > matchx; vector<int > matchy; vector<int > pre; vector<bool > visx; vector<bool > visy; vector<T> lx; vector<T> ly; vector<vector<T> > g; vector<T> slack; T inf; T res; queue<int > q; int org_n; int org_m; hungarian (int _n, int _m) { org_n = _n; org_m = _m; n = max (_n, _m); inf = numeric_limits<T>::max (); res = 0 ; g = vector<vector<T> >(n, vector <T>(n)); matchx = vector <int >(n, -1 ); matchy = vector <int >(n, -1 ); pre = vector <int >(n); visx = vector <bool >(n); visy = vector <bool >(n); lx = vector <T>(n, -inf); ly = vector <T>(n); slack = vector <T>(n); } void addEdge (int u, int v, int w) { g[u][v] = max (w, 0 ); } bool check (int v) { visy[v] = true ; if (matchy[v] != -1 ) { q.push (matchy[v]); visx[matchy[v]] = true ; return false ; } while (v != -1 ) { matchy[v] = pre[v]; swap (v, matchx[pre[v]]); } return true ; } void bfs (int i) { while (!q.empty ()) { q.pop (); } q.push (i); visx[i] = true ; while (true ) { while (!q.empty ()) { int u = q.front (); q.pop (); for (int v = 0 ; v < n; v++) { if (!visy[v]) { T delta = lx[u] + ly[v] - g[u][v]; if (slack[v] >= delta) { pre[v] = u; if (delta) { slack[v] = delta; } else if (check (v)) { return ; } } } } } T a = inf; for (int j = 0 ; j < n; j++) { if (!visy[j]) { a = min (a, slack[j]); } } for (int j = 0 ; j < n; j++) { if (visx[j]) { lx[j] -= a; } if (visy[j]) { ly[j] += a; } else { slack[j] -= a; } } for (int j = 0 ; j < n; j++) { if (!visy[j] && slack[j] == 0 && check (j)) { return ; } } } } void solve () { for (int i = 0 ; i < n; i++) { for (int j = 0 ; j < n; j++) { lx[i] = max (lx[i], g[i][j]); } } for (int i = 0 ; i < n; i++) { fill (slack.begin (), slack.end (), inf); fill (visx.begin (), visx.end (), false ); fill (visy.begin (), visy.end (), false ); bfs (i); } for (int i = 0 ; i < n; i++) { if (g[i][matchx[i]] > 0 ) { res += g[i][matchx[i]]; } else { matchx[i] = -1 ; } } cout << res << "\n" ; for (int i = 0 ; i < org_n; i++) { cout << matchx[i] + 1 << " " ; } cout << "\n" ; } };
食用方法见下
Description 从前一个和谐的班级,有 $n_{l}$ 个是男生,有 $n_{r}$ 个是女生。编号分别为 $1, \ …, \ n_{l}$ 和 $1, \ …, \ n_{r}$。
有若干个这样的条件:第 v 个男生和第 u 个女生愿意结为配偶,且结为配偶后幸福程度为 w。
请问这个班级里幸福程度之和最大是多少?
$1 \ \leq \ n_{l}, \ n_{r} \ \leq \ 400$,$1 \ \leq \ m \ \leq \ 160000$,$1 \ \leq \ w \ \leq \ 10^{9}$。
第一行三个正整数,$n_{l}, \ n_{r}, \ m$。
接下来 m 行,每行三个整数 v, u, w 表示第 v 个男生和第 u 个女生愿意结为配偶,且幸福程度为 w。保证 $1 \ \leq \ v \ \leq \ n_{l}$,$1 \ \leq \ u \ \leq \ n_{r}$,保证同一对 v, u 不会出现两次。
Output 第一行一个整数,表示幸福程度之和的最大值。
接下来一行 $n_{l}$ 个整数,描述一组最优方案。第 v 个整数表示 v 号男生的配偶的编号。如果 v 号男生没配偶请输出 0。
1 2 3 4 2 2 3 1 1 100 1 2 1 2 1 1
Sample Output
Accpted Code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 #define _CRTSECURE_NOWARNINGS #pragma warning (disable:4996) #include <cstdio> #include <cstring> #include <string> #include <cmath> #include <cctype> #include <algorithm> #include <iostream> #include <queue> #include <stack> #include <vector> #include <map> #include <set> #include <list> using namespace std;template <typename T>struct hungarian { int n; vector<int > matchx; vector<int > matchy; vector<int > pre; vector<bool > visx; vector<bool > visy; vector<T> lx; vector<T> ly; vector<vector<T> > g; vector<T> slack; T inf; T res; queue<int > q; int org_n; int org_m; hungarian (int _n, int _m) { org_n = _n; org_m = _m; n = max (_n, _m); inf = numeric_limits<T>::max (); res = 0 ; g = vector<vector<T> >(n, vector <T>(n)); matchx = vector <int >(n, -1 ); matchy = vector <int >(n, -1 ); pre = vector <int >(n); visx = vector <bool >(n); visy = vector <bool >(n); lx = vector <T>(n, -inf); ly = vector <T>(n); slack = vector <T>(n); } void addEdge (int u, int v, int w) { g[u][v] = max (w, 0 ); } bool check (int v) { visy[v] = true ; if (matchy[v] != -1 ) { q.push (matchy[v]); visx[matchy[v]] = true ; return false ; } while (v != -1 ) { matchy[v] = pre[v]; swap (v, matchx[pre[v]]); } return true ; } void bfs (int i) { while (!q.empty ()) { q.pop (); } q.push (i); visx[i] = true ; while (true ) { while (!q.empty ()) { int u = q.front (); q.pop (); for (int v = 0 ; v < n; v++) { if (!visy[v]) { T delta = lx[u] + ly[v] - g[u][v]; if (slack[v] >= delta) { pre[v] = u; if (delta) { slack[v] = delta; } else if (check (v)) { return ; } } } } } T a = inf; for (int j = 0 ; j < n; j++) { if (!visy[j]) { a = min (a, slack[j]); } } for (int j = 0 ; j < n; j++) { if (visx[j]) { lx[j] -= a; } if (visy[j]) { ly[j] += a; } else { slack[j] -= a; } } for (int j = 0 ; j < n; j++) { if (!visy[j] && slack[j] == 0 && check (j)) { return ; } } } } void solve () { for (int i = 0 ; i < n; i++) { for (int j = 0 ; j < n; j++) { lx[i] = max (lx[i], g[i][j]); } } for (int i = 0 ; i < n; i++) { fill (slack.begin (), slack.end (), inf); fill (visx.begin (), visx.end (), false ); fill (visy.begin (), visy.end (), false ); bfs (i); } for (int i = 0 ; i < n; i++) { if (g[i][matchx[i]] > 0 ) { res += g[i][matchx[i]]; } else { matchx[i] = -1 ; } } cout << res << "\n" ; for (int i = 0 ; i < org_n; i++) { cout << matchx[i] + 1 << " " ; } cout << "\n" ; } }; int main () { ios::sync_with_stdio (0 ), cin.tie (0 ); int n, m, e; cin >> n >> m >> e; hungarian<long long > solver (n, m) ; int u, v, w; for (int i = 0 ; i < e; i++) { cin >> u >> v >> w; u--, v--; solver.addEdge (u, v, w); } solver.solve (); return 0 ; }