二分图首次总结
在caioj上刷了二分图的题,比较有收获
而且关键是网上找不到题解。
我开始几道做不出,然后题解搜不到
其实这反而更有效果
很好的限制了我喜欢抄题解的习惯
只有自己去做,思维能力才能得到锻炼
一定要克制自己抄题解!!!!!!!!
最大匹配1(二分图)(元问题byscy):公牛母牛配
裸题。
注意边加的时候是有向边。
然后注意边数可能很多,不要以为是树,惯性思维
#include<bits/stdc++.h> #define REP(i, a, b) for(register int i = (a); i < (b); i++) #define _for(i, a, b) for(register int i = (a); i <= (b); i++) using namespace std; const int MAXN = 1e4 + 10; const int MAXM = 1e5 + 10; struct Edge{ int to, next; } e[MAXM << 1]; int head[MAXN], tot; int vis[MAXN], link[MAXN], n, m, k; void AddEdge(int from, int to) { e[tot] = Edge{to, head[from]}; head[from] = tot++; } bool dfs(int u) { for(int i = head[u]; ~i; i = e[i].next) { int v = e[i].to; if(vis[v]) continue; vis[v] = 1; if(!link[v] || dfs(link[v])) { link[v] = u; return true; } } return false; } int main() { memset(head, -1, sizeof(head)); tot = 0; scanf("%d%d%d", &n, &m, &k); _for(i, 1, k) { int u, v; scanf("%d%d", &u, &v); AddEdge(u, v); } int ans = 0; _for(i, 1, n) { memset(vis, 0, sizeof(vis)); if(dfs(i)) ans++; } printf("%d ", ans); return 0; }
最大二分匹配2:上课(转化模型)
可以抽象出一个模型
一个A点可以拓展出很多B点,只要选择一个B点就可以覆盖A点
一个B点只能覆盖一个A点
求最多覆盖多少A点
用二分图最大匹配就好了
#include<bits/stdc++.h> #define ID(x, y) (x - 1) * 12 + y #define REP(i, a, b) for(register int i = (a); i < (b); i++) #define _for(i, a, b) for(register int i = (a); i <= (b); i++) using namespace std; const int MAXN = 300 + 10; const int MAXM = 100; int n, a[MAXN][MAXN]; int link[MAXN], vis[MAXN]; bool find(int u) { _for(v, 1, 84) { if(!a[u][v] || vis[v]) continue; vis[v] = 1; if(!link[v] || find(link[v])) { link[v] = u; return true; } } return false; } int main() { scanf("%d", &n); _for(i, 1, n) { int t, p, q; scanf("%d", &t); while(t--) { scanf("%d%d", &p, &q); a[i][ID(p, q)] = 1; } } int ans = 0; _for(i, 1, n) { memset(vis, 0, sizeof(vis)); if(find(i)) ans++; } printf("%d ", ans); return 0; }
最大二分匹配3:地鼠(转化模型)
套用上一题的模型。能达到就连边。
#include<bits/stdc++.h> #define REP(i, a, b) for(register int i = (a); i < (b); i++) #define _for(i, a, b) for(register int i = (a); i <= (b); i++) using namespace std; const int MAXN = 100 + 10; int n, m, s, v; int a[MAXN][MAXN], vis[MAXN], link[MAXN]; double x[MAXN], y[MAXN]; int find(int u) { _for(v, 1, m) { if(!a[u][v] || vis[v]) continue; vis[v] = 1; if(!link[v] || find(link[v])) { link[v] = u; return true; } } return false; } bool judge(int i, double xx, double yy) { return sqrt(pow(x[i] - xx, 2) + pow(y[i] - yy, 2)) <= (double)s * v; } int main() { while(~scanf("%d%d%d%d", &n, &m, &s, &v)) { memset(a, 0, sizeof(a)); memset(link, 0, sizeof(link)); _for(i, 1, n) scanf("%lf%lf", &x[i], &y[i]); _for(i, 1, m) { double xx, yy; scanf("%lf%lf", &xx, &yy); _for(j, 1, n) a[j][i] = judge(j, xx, yy); } int ans = 0; _for(i, 1, n) { memset(vis, 0, sizeof(vis)); if(find(i)) ans++; } printf("%d ", n - ans); } return 0; }
最小覆盖1(二分图)(元问题byscy)
最小覆盖=最大匹配
代码和最大匹配的一模一样,不贴了
最小覆盖2(模型转换:地雷)
很经典的一道题
其实要理解透彻,抽象出一个模型
我的理解是,对于一个地雷,可以被这一行和这一列的炮击毁。
所以把行看作X集合,列看作Y集合,地雷看作边
这样建图后,一个点,也就是一行或者一列,就可以消灭这一行或这一列的地雷,
也就是说一个点可以消灭与其相连的边
那么显然最小覆盖了
要理解本质
#include<bits/stdc++.h> #define REP(i, a, b) for(register int i = (a); i < (b); i++) #define _for(i, a, b) for(register int i = (a); i <= (b); i++) using namespace std; const int MAXN = 500 + 10; const int MAXM = 1e4 + 10; struct Edge{ int to, next; } e[MAXM]; int head[MAXN], tot; int vis[MAXN], link[MAXN], n, m, k; void AddEdge(int from, int to) { e[tot] = Edge{to, head[from]}; head[from] = tot++; } bool dfs(int u) { for(int i = head[u]; ~i; i = e[i].next) { int v = e[i].to; if(vis[v]) continue; vis[v] = 1; if(!link[v] || dfs(link[v])) { link[v] = u; return true; } } return false; } int main() { memset(head, -1, sizeof(head)); tot = 0; scanf("%d%d", &n, &m); _for(i, 1, m) { int u, v; scanf("%d%d", &u, &v); AddEdge(u, v); } int ans = 0; _for(i, 1, n) { memset(vis, 0, sizeof(vis)); if(dfs(i)) ans++; } printf("%d ", ans); return 0; }
最小覆盖2(模型转换:草场淹水)
这题想了巨久。1.5h吧
其实这题可以用来检验你有没有理解上一题的本质
在这道题
每个点可以被横着的木板和竖着的木板消灭
那么类比上一题,就把横着的木板和竖着的木板作为X集合和Y集合
这个点就作为边,就ok了
注意要预处理出每一个木板的编号
不过我一开始觉得要超时。因为匈牙利算法是n三方的
然而这个编号可以到2500(大概这个数量级,实际上1000多左右,因为点和点可以合并)
但是交上去是0ms
可能是数据水,或者匈牙利算法实际上远远达不到n三方。
#include<bits/stdc++.h> #define REP(i, a, b) for(register int i = (a); i < (b); i++) #define _for(i, a, b) for(register int i = (a); i <= (b); i++) using namespace std; const int MAXN = 50; const int MAXM = 2500; struct Edge{ int to, next; } e[MAXM * MAXM]; int head[MAXM], tot; int vis[MAXM], link[MAXM], n, m, id; int a1[MAXN][MAXN], a2[MAXN][MAXN]; char s[MAXN][MAXN]; void AddEdge(int from, int to) { e[tot] = Edge{to, head[from]}; head[from] = tot++; } bool dfs(int u) { for(int i = head[u]; ~i; i = e[i].next) { int v = e[i].to; if(vis[v]) continue; vis[v] = 1; if(!link[v] || dfs(link[v])) { link[v] = u; return true; } } return false; } int main() { memset(head, -1, sizeof(head)); tot = 0; scanf("%d%d", &n, &m); _for(i, 1, n) scanf("%s", s[i] + 1); id = 0; _for(j, 1, m) _for(i, 1, n) if(s[i][j] == '*') { int t = i; id++; while(s[t][j] == '*') a2[t++][j] = id; i = t - 1; } id = 0; _for(i, 1, n) _for(j, 1, m) if(s[i][j] == '*') { int t = j; id++; while(s[i][t] == '*') a1[i][t++] = id; j = t - 1; } _for(i, 1, n) _for(j, 1, m) if(s[i][j] == '*') AddEdge(a1[i][j], a2[i][j]); int ans = 0; _for(i, 1, id) { memset(vis, 0, sizeof(vis)); if(dfs(i)) ans++; } printf("%d ", ans); return 0; }
最后剩下一道题,一般图的最大独立子集。
不会……