二分图的匹配
二分图判定问题。
#include <bits/stdc++.h> using namespace std; const int maxn = 2e4 + 50; struct edge { int v, cost; }; vector<edge> g[maxn]; int vis[maxn]; int flag = 0; void dfs(int u, int color, int mid) { vis[u] = color; for(int i = 0; i < (int)g[u].size(); i++) { int v = g[u][i].v; if(g[u][i].cost > mid) ///这里必须是大于mid,代表可拆成二分图 我们求的就是恰好不能拆成二分图的mid { if(vis[v] == 0) { dfs(v, 3 - color, mid); } else if(vis[v] == color) { flag = 1; return; } } } } int check(int mid, int n) { for(int i = 1; i <= n; i++) vis[i] = 0; flag = 0; for(int i = 1; i <= n; i++) { if(!vis[i]) dfs(i, 1, mid); } return (!flag); } int main() { int n, m; scanf("%d %d", &n, &m); int l = 0, r = 0; for(int i = 1; i <= m; i++) { int u, v, w; scanf("%d %d %d", &u, &v, &w); g[u].push_back({v, w}); g[v].push_back({u, w}); r = max(r, w); } int ans = 0; while(l <= r) { int mid = (l + r) >> 1; if(check(mid, n)) { ans = mid; r = mid - 1; } else { l = mid + 1; } } printf("%d\n", ans); return 0; }
二分图匹配模型的两个要素:
“0元素”:节点分成独立的两个集合,每个集合内部有0条边。
“1元素”:每个节点最多只能与1条匹配边相连。
对于本题,可以把棋盘中相邻格子染成黑白相间,那么同一块骨牌不可能覆盖两个颜色一样的格子,集合内没有边。(0元素)
每个格子最多只能被1块骨牌覆盖,最多有一条边。(1元素)
二分图的最大匹配问题。
#include <bits/stdc++.h> using namespace std; const int maxn = 1e4 + 50; int ban[105][105]; vector<int> lepart; int n; int check(int x, int y) { if(x >= 1 && x <= n && y >= 1 && y <= n && ban[x][y] == 0) return 1; else return 0; } vector<int> g[maxn]; int vis[maxn], match[maxn]; int dfs(int x) { for(int i = 0; i < g[x].size(); i++) { int y = g[x][i]; if(!vis[y]) { vis[y] = 1; if(!match[y] || dfs(match[y])) { match[y] = x; return true; } } } return false; } void add(int u, int v) { g[u].push_back(v); g[v].push_back(u); } int main() { int t; scanf("%d %d", &n, &t); for(int i = 1; i <= t; i++) { int x, y; scanf("%d %d", &x, &y); ban[x][y] = 1; /// 禁用了 } for(int i = 1; i <= n; i++) { for(int j = 1; j <= n; j++) { if((i + j) % 2 == 0 && ban[i][j] == 0) { int node = (i - 1) * n + j; lepart.push_back(node); if(check(i - 1, j)) add(node, node - n); if(check(i + 1, j)) add(node, node + n); if(check(i, j - 1)) add(node, node - 1); if(check(i, j + 1)) add(node, node + 1); } } } int ans = 0; // printf("%d\n", lepart.size()); for(int i = 0; i < (int)lepart.size(); i++) { memset(vis, 0, sizeof(vis)); if(dfs(lepart[i])) ans++; } printf("%d\n", ans); }
因为每行每列至多只能有一个车,我们寻找两个元素:
“1元素”:每行每列至多有一个车,也就是最多有一条匹配边。
“0元素”:行与行之间不会有边相连。
二分图的最大匹配问题。
#include <bits/stdc++.h> using namespace std; const int maxn = 405; int ban[205][205]; int n, m; vector<int> g[maxn]; int vis[maxn], match[maxn]; int dfs(int x) { for(int i = 0; i < g[x].size(); i++) { int y = g[x][i]; if(!vis[y]) { vis[y] = 1; if(!match[y] || dfs(match[y])) { match[y] = x; return true; } } } return false; } void add(int u, int v) { g[u].push_back(v); g[v].push_back(u); } int main() { int t; scanf("%d %d %d", &n, &m, &t); for(int i = 1; i <= t; i++) { int x, y; scanf("%d %d", &x, &y); ban[x][y] = 1; /// 禁用了 } for(int i = 1; i <= n; i++) { for(int j = 1; j <= m; j++) { if(ban[i][j] == 0) { add(i, n + j); } } } int ans = 0; for(int i = 1; i <= n; i++) { memset(vis, 0, sizeof(vis)); if(dfs(i)) ans++; } printf("%d\n", ans); }
本题是二分图的多重匹配问题。
每个防御塔可以发射多枚导弹,但每个入侵者只要被一个打击,就能击毁,那每个入侵者只能与一个导弹连边。(“1元素”)
题目问最少需要多少分钟才能击退所有入侵者,答案符合单调性,可以二分来做。
我们假设在$mid$时间内,每个防御塔能够发射$P$个导弹,第$i$个防御塔的第$k(1≤k≤P)$个防御塔能够击毁第$j$个入侵者,就在导弹和入侵者之间连一条边。
对入侵者跑匈牙利算法,若每个入侵者都能找到匹配,则此$mid$可行。
#include <bits/stdc++.h> using namespace std; const int maxn = 3e5; vector<int> g[maxn]; int vis[maxn], match[maxn]; int dfs(int x) { for(int i = 0; i < g[x].size(); i++) { int y = g[x][i]; if(!vis[y]) { vis[y] = 1; if(!match[y] || dfs(match[y])) { match[y] = x; return true; } } } return false; } void add(int u, int v) { g[u].push_back(v); g[v].push_back(u); } #define x1 xx1 #define x2 xx2 #define y1 yy1 #define y2 yy2 int x1[55], y1[55], x2[55], y2[55]; int n, m, t1, t2, v; double pf(double x) { return x * x; } const double eps = 1e-8; int check(double mid, double t1) { int cnt = (mid + t2 + eps) / (t1 + t2); for(int i = 1; i <= n * cnt + m; i++) g[i].clear(), match[i] = 0; for(int i = 1; i <= n; i++) { for(int j = 1; j <= m; j++) { double dis = sqrt(pf(x2[i] - x1[j]) + pf(y2[i] - y1[j])); double t = dis / v + t1; for(int k = 1; k <= cnt; k++) { if(t < mid || fabs(t - mid) <= eps) { add((i - 1) * cnt + k, n * cnt + j); t = t + t1 + t2; } else break; } } } int ans = 0; for(int i = cnt * n + 1; i <= n * cnt + m; i++) { for(int j = 1; j <= n * cnt + m; j++) vis[j] = 0; if(dfs(i)) ans++; } return ans >= m; } int main() { scanf("%d %d %d %d %d", &n, &m, &t1, &t2, &v); double fuckminute = 1.0 * t1 / 60; for(int i = 1; i <= m; i++) scanf("%d %d", &x1[i], &y1[i]); for(int i = 1; i <= n; i++) scanf("%d %d", &x2[i], &y2[i]); double l = 0, r = 1e5; int cnt = 100; double ans = 0; while(cnt--) { double mid = (l + r) / 2; if(check(mid, fuckminute)) { ans = mid; r = mid; } else l = mid; } printf("%.6f\n", ans); }
本题是二分图带权最大匹配问题。
所有线段不相交,就是所有线段的长度之和最小。
注意KM模板的正确性。
#include <cstdio> #include <algorithm> #include <cmath> #include <cstring> using namespace std; const int maxn = 105; int xx1[maxn], yy1[maxn]; int xx2[maxn], yy2[maxn]; double w[maxn][maxn]; double pf(int x) { return (double)x * x; } double la[maxn], lb[maxn]; bool va[maxn], vb[maxn]; int match[maxn]; int n; double delta; const double eps = 1e-10; bool dfs(int x) { va[x] = 1; ///在交错树中 for(int y = 1; y <= n; y++) { if(!vb[y]) { if(fabs(la[x] + lb[y] - w[x][y]) <= eps) { vb[y] = 1; if(!match[y] || dfs(match[y])) { match[y] = x; return true; } } } } return false; } int ans[maxn]; int KM() { for(int i = 1; i <= n; i++) { la[i] = -1e12; lb[i] = 0; for(int j = 1; j <= n; j++) { la[i] = max(la[i], w[i][j]); } } for(int i = 1; i <= n; i++) { while(1) { memset(va, 0, sizeof(va)); memset(vb, 0, sizeof(vb)); if(dfs(i)) break; delta = 1e9; for(int j = 1; j <= n; j++) { if(va[j]) { for(int k = 1; k <= n; k++) { if(!vb[k]) delta = min(delta, la[j] + lb[k] - w[j][k]); } } } for(int j = 1; j <= n; j++) { if(va[j]) la[j] -= delta; if(vb[j]) lb[j] += delta; } } } for(int i = 1; i <= n; i++) ans[match[i]] = i; } int main() { scanf("%d", &n); for(int i = 1; i <= n; i++) { scanf("%d %d", &xx1[i], &yy1[i]); } for(int i = 1; i <= n; i++) { scanf("%d %d", &xx2[i], &yy2[i]); } for(int i = 1; i <= n; i++) { for(int j = 1; j <= n; j++) { w[i][j] = -sqrt(pf(xx1[i] - xx2[j]) + pf(yy1[i] - yy2[j])); } } KM(); for(int i = 1; i <= n; i++) { printf("%d\n", ans[i]); } return 0; }
本题是二分图最小点覆盖。
模型特点是:“2要素”:每条边有2个端点,二者至少选择一个。
本题的2要素:每个任务要么在机器A上运行,要么在模式B上运行。
这题一开始就在模式0下,所有一开始就把和0相连的先做了。
#include <iostream> #include <algorithm> #include <cstring> #include <vector> #include <cstdio> using namespace std; const int maxn = 205; vector<int> g[maxn]; int vis[maxn], match[maxn]; int dfs(int x) { vis[x] = 1; for(int i = 0; i < g[x].size(); i++) { int y = g[x][i]; if(!vis[y]) { vis[y] = 1; if(!match[y] || dfs(match[y])) { match[y] = x; return true; } } } return false; } void add(int u, int v) { g[u].push_back(v); g[v].push_back(u); } int tmp[maxn]; int main() { int n, m, k; while(scanf("%d", &n) != EOF && n) { scanf("%d %d", &m, &k); for(int i = 1; i <= n + m; i++) { g[i].clear(); match[i] = 0; tmp[i] = 0; } for(int i = 1; i <= k; i++) { int ii, x, y; scanf("%d %d %d", &ii, &x, &y); x++, y++; if(x != 1 && y != 1) add(x, y + n); } for(int i = 1; i <= n; i++) { memset(vis, 0, sizeof(vis)); dfs(i); } memset(vis, 0, sizeof(vis)); for(int i = n + 1; i <= n + m; i++) { tmp[match[i]] = 1; } for(int i = 1; i <= n; i++) { if(!tmp[i]) dfs(i); } int ans = 0; for(int i = 1; i <= n; i++) { if(!vis[i]) ans++; } for(int i = n + 1; i <= n + m; i++) { if(vis[i]) ans++; } printf("%d\n", ans); } return 0; }
本题也是二分图最小点覆盖问题。
寻找“2”要素,每个泥泞点要么被横着的木板挡住,要么被竖着的木板挡住,二者至少选择一个,这时候每个泥点相当于边。
#include <algorithm> #include <cstring> #include <vector> #include <cstdio> using namespace std; const int maxn = 2505; vector<int> g[maxn]; int vis[maxn], match[maxn]; int dfs(int x) { vis[x] = 1; for(int i = 0; i < g[x].size(); i++) { int y = g[x][i]; if(!vis[y]) { vis[y] = 1; if(!match[y] || dfs(match[y])) { match[y] = x; return true; } } } return false; } void add(int u, int v) { g[u].push_back(v); g[v].push_back(u); } char s[55][55]; int l[55][55], c[55][55]; int main() { int n, m; scanf("%d %d", &n, &m); for(int i = 1; i <= n; i++) { scanf("%s", s[i] + 1); } int num1 = 0; memset(l, 0, sizeof(l)); memset(c, 0, sizeof(c)); for(int i = 1; i <= n; i++) { for(int j = 1; j <= m; j++) { if(s[i][j] == '*') { if(!l[i][j - 1]) { l[i][j] = ++num1; } else l[i][j] = l[i][j - 1]; } } } int num2 = num1; for(int j = 1; j <= m; j++) { for(int i = 1; i <= n; i++) { if(s[i][j] == '*') { if(!c[i - 1][j]) { c[i][j] = ++num2; } else c[i][j] = c[i - 1][j]; } } } for(int i = 1; i <= n; i++) { for(int j = 1; j <= m; j++) { if(s[i][j] == '*') { add(l[i][j], c[i][j]); } } } int ans = 0; for(int i = 1; i <= num1; i++) { memset(vis, 0, sizeof(vis)); if(dfs(i)) ans++; } printf("%d\n", ans); return 0; }