二分图最大匹配:
hopcroft-carp算法 O(sqrt(n)m) (算是改进版的匈牙利算法吧
#include <bits/stdc++.h> using namespace std; const int maxn = 501; const int maxm = 250001; const int inf = 0x3f3f3f3f; int n, m, nl; int tot, head[maxn]; int ans, linkx[maxn], linky[maxn]; int dx[maxn], dy[maxn]; struct E { int to, next; }edge[maxm]; //这里千万要注意,如果题目是双向边的话,这里的N要开2*N inline void read(int& ans) { int x = 0, f = 1; char ch = getchar(); while (ch<'0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); } while ( ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); } ans = x * f; } void inti() { } //加边 inline void add_edge(int u, int v) { edge[tot].to = v; edge[tot].next = head[u]; head[u] = tot++; if (!linkx[u] && !linky[v]) linky[v] = u, linkx[u] = v, ++ ans; } bool bfs() { bool ret=0; queue<int> q; memset(dx, 0, sizeof(dx)), memset(dy, 0, sizeof(dy)); for (int i = 1; i <= nl; ++ i) if (!linkx[i]) q.push(i); while (!q.empty()) { int u = q.front(); q.pop(); for (int i = head[u]; ~i; i = edge[i].next) { int v = edge[i].to; if (!dy[v]) { dy[v] = dx[u] + 1; if (!linky[v]) ret = 1; else dx[linky[v]] = dy[v]+1, q.push(linky[v]); } } } return ret; } bool dfs(int u) { for (int i = head[u]; ~i; i = edge[i].next) { int v = edge[i].to; if (dy[v] == dx[u] + 1) { dy[v] = 0; if (!linky[v] || dfs(linky[v])) { linky[v] = u, linkx[u] = v; return 1; } } } return 0; } int main() { int a, b; memset(head, -1, sizeof(head)); read(n), read(m); nl = n; //nl代表的是左匹配点集,要注意n是否等于nl for (int i = 1; i <= m; ++ i) read(a), read(b), add_edge(a,b); while(bfs()) { for (int i = 1; i <= n; ++ i) if (!linkx[i] && dfs(i)) ++ ans; } bool vis[n+1] = {0}; stack<int> s; //linky表示的是pre点, linkx表示的是next点 for (int i = 1; i <= n; ++ i) { if (vis[i]) continue; int cnt = 0; int t = i; int res[n+1]; vis[t] = 1; s.push(t); while (linky[t]) { t = linky[t]; s.push(t); vis[t] = 1; } while (!s.empty()) { res[cnt++] = s.top(); s.pop(); } t = i; while (linkx[t]) { t = linkx[t]; res[cnt++] = t; vis[t] = 1; } for (int i = 0; i < cnt-1; ++ i) cout << res[i] << " "; cout << res[cnt-1] << endl; } printf("%d ",n-ans); // 这段循环是用来输出跟谁匹配 // for (int i = 1; i <= n; ++ i) // printf("%d ",linkx[i]); return 0; }
顺便再存一手匈牙利板子好了
vector<int> E[maxn]; int nx, ny; int mx[maxn], my[maxn], vis[maxn]; bool DFS(int u) { for (auto v : E[u]) { if (!vis[v]) { vis[v] = 1; if (my[v] == 0 || DFS(my[v])) { my[v] = u; mx[u] = v; return 1; } } } return 0; } int hungary() { memset(mx, 0, sizeof mx); memset(my, 0, sizeof my); int ans = 0; for (int i = 1; i <= nx; ++ i) { if (mx[i] == 0) { memset(vis, 0, sizeof vis); if (DFS(i)) ans ++; } } return ans; } ///主函数需要给nx, ny赋值 nx = ny = n*n;
基础概念:
点的概念:
a.点覆盖集:无向图G的一个点集,使得该图中所有边都至少有一点端点在该集合内。
b.点独立集:无向图G的一个点集,使得任两个在该集合中的点在原图中不相邻。
最大独立集:点独立集中元素个数最大的独立集,那么此独立集元素个数k就是这个图的最大独立数。
c.最小点覆盖集:无向图G中点数最少的点覆盖集
d.最大点独立集:无向图G中,点数最多的点独立集
e.最小点权覆盖集:带点权的无向图中,点权之和最小的点覆盖集
f.最大点权独立集:实在带点权无向图中,点权之和最大的点独立集
g.无向图的最大团: 从V个顶点选出k个顶,使得这k个顶构成一个完全图,即该子图任意两个顶都有直接的边。
h.最小顶点覆盖:用最少的点(左右两边集合的点)让每条边都至少和其中一个点关联。
边的概念:
a.边覆盖集:就是G中所有的顶点都是E*中某条边的邻接顶点(边覆盖顶点),一条边只能覆盖2个顶点。
注意:在无向图中存在用尽量少的边去“覆盖”住所有的顶点,所以边覆盖集有极小与最小的区别。
b.极小边覆盖:若边覆盖E*中的任何真子集都不是边覆盖集,则称E*是极小边覆盖集。
c.最小边覆盖:边数最小的边覆盖称为最小边覆盖,通俗地讲,就是极小边覆盖中的最小的一个集合。
最小边覆盖 = 最大独立集 = n - 最大匹配,这个是二分图上的一个性质。
d.最小路径覆盖(原图不一定是二分图,但必须是有向图,拆点构造二分图):在图中找一些路径,使之覆盖了图中的所有顶点,且 任何一个顶点有且只有一条路径与之关联。最小路径覆盖 = |V| - 最大匹配数
最小路径覆盖相较于最小边覆盖的不同:不要求给的图是二分图,而是要求是N x N的有向图,不能有环,然后根据原图构造二分图,构造方法是将点一分为二,
如,i分为i1和i2然后如果i和j有边,那么就在i1和j2之间连一条边。由此构成二分图
然后最小路径覆盖 = n-m,n为原图的点的个数,m为新造二分图的最大匹配。
证明也是特别简单的,根据定义最小路径覆盖里要求同一个点只可以属于一条路径,
即路径时不可以开叉的,如果在二分图里选两条有公共点的边那么反应在原图上就是路径有岔路了,
所以二分图里选的边必须是无公共交点的,这就是转化到最大匹配了。
二分图中的性质:
最大团 = 补图的最大独立集
最小边覆盖 = 二分图最大独立集 = |V| - 最小路径覆盖
最小路径覆盖 = |V| - 最大匹配数
最小顶点覆盖 = 最大匹配数
最小顶点覆盖 + 最大独立数 = |V|
最小割 = 最小点权覆盖集 = 点权和 - 最大点权独立集
这里要聊几种常见题型:
一、最大匹配
(一般只考最大匹配的都是思维题)
匹配是图中一些边的集合,且集合中任意两条边都没有公共点,所有的匹配中,边数最多的就是最大匹配。
下面这道就是比较经典的,较为简单的思维题了。
#include <bits/stdc++.h> using namespace std; const int maxn = 501; const int maxm = 250001; const int inf = 0x3f3f3f3f; int n, m; int tot, head[maxn]; int ans, linkx[maxn], linky[maxn]; int dx[maxn], dy[maxn]; struct E { int to, next; }edge[maxm]; //这里千万要注意,如果题目是双向边的话,这里的N要开2*N inline void read(int& ans) { int x = 0, f = 1; char ch = getchar(); while (ch<'0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); } while ( ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); } ans = x * f; } //加边 inline void add_edge(int u, int v) { edge[tot].to = v; edge[tot].next = head[u]; head[u] = tot++; if (!linkx[u] && !linky[v]) linky[v] = u, linkx[u] = v, ++ ans; } void init() { tot = ans = 0; memset(head, -1, sizeof(head)); memset(linkx, 0, sizeof(linkx)); memset(linky, 0, sizeof(linky)); } bool bfs() { bool ret = 0; queue<int> q; memset(dx, 0, sizeof(dx)), memset(dy, 0, sizeof(dy)); for (int i = 1; i <= n; ++ i) if (!linkx[i]) q.push(i); while (!q.empty()) { int u = q.front(); q.pop(); for (int i = head[u]; ~i; i = edge[i].next) { int v = edge[i].to; if (!dy[v]) { dy[v] = dx[u] + 1; if (!linky[v]) ret = 1; else dx[linky[v]] = dy[v]+1, q.push(linky[v]); } } } return ret; } bool dfs(int u) { for (int i = head[u]; ~i; i = edge[i].next) { int v = edge[i].to; if (dy[v] == dx[u] + 1) { dy[v] = 0; if (!linky[v] || dfs(linky[v])) { linky[v] = u, linkx[u] = v; return 1; } } } return 0; } int main() { int T; read(T); while (T--) { read(n); init(); for (int i = 1; i <= n; ++ i) for (int j = 1; j <= n; ++ j) { int temp; read(temp); if (temp) add_edge(i, j); } while (bfs()) { for (int i = 1; i <= n; ++ i) if (!linkx[i] && dfs(i)) ++ ans; } if (ans == n) printf("Yes "); else printf("No "); } return 0; }
最大匹配的重要路径(最大匹配的交集)
#include <bits/stdc++.h> using namespace std; const int maxn = 2500; const int maxm = 200000+500; const int inf = 0x3f3f3f3f; int n, m, r, c, k; int tot, head[maxm]; int ans, linkx[maxm], linky[maxm]; int dx[maxm], dy[maxm]; int g[maxn][maxn]; int col[maxn]; struct E { int to, next; }edge[maxm]; //这里千万要注意,如果题目是双向边的话,这里的N要开2*N inline int read() { int ans = 0; int x = 0, f = 1; char ch = getchar(); while (ch<'0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); } while ( ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); } return ans = x * f; } inline void add_edge(int u, int v) { edge[tot].to = v; edge[tot].next = head[u]; head[u] = tot++; if (!linkx[u] && !linky[v]) linky[v] = u, linkx[u] = v, ++ ans; } void init() { tot = ans = 0; // r = c = 1; memset(head, -1, sizeof(head)); memset(linkx, 0, sizeof(linkx)); memset(linky, 0, sizeof(linky)); memset(col, 0, sizeof(col)); memset(g, 0, sizeof(g)); } bool bfs() { bool ret = 0; queue<int> q; memset(dx, 0, sizeof(dx)), memset(dy, 0, sizeof(dy)); for (int i = 1; i <= n; ++ i) if (!linkx[i]) q.push(i); while (!q.empty()) { int u = q.front(); q.pop(); for (int j = 1; j <= m; ++ j) { if (g[u][j] && !dy[j]) { dy[j] = dx[u] + 1; if (!linky[j]) ret = 1; else dx[linky[j]] = dy[j]+1, q.push(linky[j]); } } } return ret; } bool dfs(int u) { for (int j = 1; j <= m; ++ j) { if (g[u][j] && dy[j] == dx[u] + 1) { dy[j] = 0; if (!linky[j] || dfs(linky[j])) { linky[j] = u, linkx[u] = j; return 1; } } } return 0; } int KM() { int res = 0;// = ans; while (bfs()) { for (int i = 1; i <= n; ++ i) if (!linkx[i] && dfs(i)) ++ res; } return res; } int main() { int T=0; while (~scanf("%d%d%d", &n, &m, &k)) { init(); for (int i = 1; i <= k; ++ i) { int u = read(), v = read(); g[u][v] = 1; } int rec_ans = KM(); int rec[n+1]; int cnt = 0; for (int i = 1; i <= n; ++ i) rec[i] = linkx[i]; for (int i = 1; i <= n; ++ i) { //初始化挺重要的 memset(linkx, 0, sizeof(linkx)), memset(linky, 0, sizeof(linky)); int temp = g[i][rec[i]]; g[i][rec[i]] = 0; if (KM() < rec_ans) ++ cnt; g[i][rec[i]] = temp; } cout << "Board " << ++ T << " have " << cnt << " important blanks for " << rec_ans << " chessmen." << endl; } // 这段循环是用来输出跟谁匹配 // printf("%d ", ans); // for (int i = 1; i <= n*m; ++ i) // if (linkx[i]) printf("%d %d ",i, linkx[i]-n*m); return 0; } /* 2 2 4 1 1 1 2 2 1 2 2 */
二、最小点覆盖(铺板子)
简而言之就是最少的点跟全部边有关。
求法:最小点覆盖 = 最大匹配数
UPD2020.9.19:
突然发现之前写的话并不是很好理解。看了Marix67的博文才算理解为什么上述等式成立、
因为增广路定理是当增广路不存在时,匹配数最大。那么增广路是什么呢,就是一端被匹配,另一端没被匹配的边,叫做增广路。
那么增广路不存在的意思代表的就是所有得点都被覆盖上了,正好符合最小点覆盖的意义。所以上述等式成立。
这些题的预处理还是能好好参考的。
三、最大独立集(点集)(互不影响 / 攻击):
其实从名字理解就会比较清楚。就是求最多数量的点,使其互不影响(无边相连)。
求法:最大独立集 = 图中点的个数 - 最大匹配 (具体就不证明了)
UPD2020.9.19:这个要从上一个概念最小点覆盖理解,所有点数-最小覆盖点数 = 独立集数。
简单说说我对求法的理解:一个点会有一个影响范围,比如说棋盘摆放骑士问题,一个骑士可以攻击八个点,那么这八个点就是他的影响范围。
我们将每个可以摆放骑士的点,跟摆放在点上所影响的八个点建边,进行最大匹配。那么匹配的意义是什么呢?将会互相影响的骑士位置匹配。
那么最大匹配代表的就是,最多的互相影响的骑士位置。
那么可以摆放骑士位置的个数(总点数)- 最大相互影响的骑士位置数(最大匹配) = 不会互相影响的骑士个数(最大独立集)
但注意这里是双向边,所以最大匹配数应该是要 / 2的
给几道题吧:
顺序给出我的AC代码:
#include <bits/stdc++.h> using namespace std; const int maxn = 300+10; const int maxm = 100000+100; const int inf = 0x3f3f3f3f; int n, m, r, c; int tot, head[maxm]; int ans, linkx[maxm], linky[maxm]; int dx[maxm], dy[maxm]; char g[maxn][maxn]; int id[maxn][maxn]; int dir[8][2]={-1,2,-1,-2, 1,2,1,-2, 2,1, 2,-1, -2,1,-2,-1}; struct E { int to, next; }edge[maxm]; //这里千万要注意,如果题目是双向边的话,这里的N要开2*N inline void read(int& ans) { int x = 0, f = 1; char ch = getchar(); while (ch<'0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); } while ( ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); } ans = x * f; } inline void add_edge(int u, int v) { edge[tot].to = v; edge[tot].next = head[u]; head[u] = tot++; if (!linkx[u] && !linky[v]) linky[v] = u, linkx[u] = v, ++ ans; } void init() { tot = ans = 0; memset(head, -1, sizeof(head)); memset(linkx, 0, sizeof(linkx)); memset(linky, 0, sizeof(linky)); } bool bfs() { bool ret = 0; queue<int> q; memset(dx, 0, sizeof(dx)), memset(dy, 0, sizeof(dy)); for (int i = 1; i <= n*n; ++ i) if (!linkx[i]) q.push(i); while (!q.empty()) { int u = q.front(); q.pop(); for (int i = head[u]; ~i; i = edge[i].next) { int v = edge[i].to; if (!dy[v]) { dy[v] = dx[u] + 1; if (!linky[v]) ret = 1; else dx[linky[v]] = dy[v]+1, q.push(linky[v]); } } } return ret; } bool dfs(int u) { for (int i = head[u]; ~i; i = edge[i].next) { int v = edge[i].to; if (dy[v] == dx[u] + 1) { dy[v] = 0; if (!linky[v] || dfs(linky[v])) { linky[v] = u, linkx[u] = v; return 1; } } } return 0; } int main() { init(); ios::sync_with_stdio(0); cin.tie(0); cin >> n; for (int i = 1; i <= n; ++ i) for (int j = 1; j <= n; ++ j) cin >> g[i][j], id[i][j] = (i-1)*n+j; int res = 0; for (int i = 1; i <= n; ++ i) { for (int j = 1; j <= n; ++ j) { if (g[i][j] == '0') { ++ res; for (int k = 0; k < 8; ++ k) { int tx = i + dir[k][0], ty = j+ dir[k][1]; if (ty < 1 || ty > n || tx < 1 || tx > n || g[tx][ty] == '1') continue; add_edge(id[i][j], id[tx][ty]); } } } } while (bfs()) { for (int i = 1; i <= n*n; ++ i) if (!linkx[i] && dfs(i)) ++ ans; } cout << res-ans/2 << endl; //这段循环是用来输出跟谁匹配 // printf("%d ", ans); // for (int i = 1; i <= n; ++ i) // if (linkx[i]) printf("%d %d ",i, linkx[i]); return 0; }
#include <bits/stdc++.h> using namespace std; const int maxn = 300+10; const int maxm = 1000000+100; const int inf = 0x3f3f3f3f; int n, m, k; int tot, head[maxm]; int ans, linkx[maxm], linky[maxm]; int dx[maxm], dy[maxm]; char g[maxn][maxn]; int id[maxn][maxn]; int dir[8][2]={-1,3,-1,-3, 1,3,1,-3, 3,1, 3,-1, -3,1,-3,-1}; struct E { int to, next; }edge[maxm]; //这里千万要注意,如果题目是双向边的话,这里的N要开2*N inline void read(int& ans) { int x = 0, f = 1; char ch = getchar(); while (ch<'0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); } while ( ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); } ans = x * f; } inline void add_edge(int u, int v) { edge[tot].to = v; edge[tot].next = head[u]; head[u] = tot++; if (!linkx[u] && !linky[v]) linky[v] = u, linkx[u] = v, ++ ans; } void init() { tot = ans = 0; memset(head, -1, sizeof(head)); memset(linkx, 0, sizeof(linkx)); memset(linky, 0, sizeof(linky)); } bool bfs() { bool ret = 0; queue<int> q; memset(dx, 0, sizeof(dx)), memset(dy, 0, sizeof(dy)); for (int i = 1; i <= n*m; ++ i) if (!linkx[i]) q.push(i); while (!q.empty()) { int u = q.front(); q.pop(); for (int i = head[u]; ~i; i = edge[i].next) { int v = edge[i].to; if (!dy[v]) { dy[v] = dx[u] + 1; if (!linky[v]) ret = 1; else dx[linky[v]] = dy[v]+1, q.push(linky[v]); } } } return ret; } bool dfs(int u) { for (int i = head[u]; ~i; i = edge[i].next) { int v = edge[i].to; if (dy[v] == dx[u] + 1) { dy[v] = 0; if (!linky[v] || dfs(linky[v])) { linky[v] = u, linkx[u] = v; return 1; } } } return 0; } int main() { init(); ios::sync_with_stdio(0); cin.tie(0); cin >> n >> m >> k; memset(g, 0, sizeof (g)); for (int i = 0; i < k ; ++ i) { int x, y; cin >> x >> y; g[x][y] = 1; } int res = 0; for (int i = 1; i <= n; ++ i) { for (int j = 1; j <= m; ++ j) { if (!g[i][j]) { ++ res; for (int k = 0; k < 8; ++ k) { int tx = i + dir[k][0], ty = j+ dir[k][1]; if (ty < 1 || ty > m || tx < 1 || tx > n || g[tx][ty]) continue; add_edge((i-1)*m+j, (tx-1)*m+ty); } } } } while (bfs()) { for (int i = 1; i <= n*m; ++ i) if (!linkx[i] && dfs(i)) ++ ans; } cout << res-ans/2 << endl; //这段循环是用来输出跟谁匹配 // printf("%d ", ans); // for (int i = 1; i <= n*m; ++ i) // if (linkx[i]) printf("%d %d ",i, linkx[i]); return 0; }
#include <bits/stdc++.h> using namespace std; const int maxn = 300+10; const int maxm = 1000000+100; const int inf = 0x3f3f3f3f; int n, m, k; int tot, head[maxm]; int ans, linkx[maxm], linky[maxm]; int dx[maxm], dy[maxm]; char g[maxn][maxn]; int id[maxn][maxn]; int dir[8][2]={-1,2,-1,-2, 1,2,1,-2, 2,1, 2,-1, -2,1,-2,-1}; struct E { int to, next; }edge[maxm]; //这里千万要注意,如果题目是双向边的话,这里的N要开2*N inline void read(int& ans) { int x = 0, f = 1; char ch = getchar(); while (ch<'0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); } while ( ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); } ans = x * f; } inline void add_edge(int u, int v) { edge[tot].to = v; edge[tot].next = head[u]; head[u] = tot++; if (!linkx[u] && !linky[v]) linky[v] = u, linkx[u] = v, ++ ans; } void init() { tot = ans = 0; memset(head, -1, sizeof(head)); memset(linkx, 0, sizeof(linkx)); memset(linky, 0, sizeof(linky)); } bool bfs() { bool ret = 0; queue<int> q; memset(dx, 0, sizeof(dx)), memset(dy, 0, sizeof(dy)); for (int i = 1; i <= n*n; ++ i) if (!linkx[i]) q.push(i); while (!q.empty()) { int u = q.front(); q.pop(); for (int i = head[u]; ~i; i = edge[i].next) { int v = edge[i].to; if (!dy[v]) { dy[v] = dx[u] + 1; if (!linky[v]) ret = 1; else dx[linky[v]] = dy[v]+1, q.push(linky[v]); } } } return ret; } bool dfs(int u) { for (int i = head[u]; ~i; i = edge[i].next) { int v = edge[i].to; if (dy[v] == dx[u] + 1) { dy[v] = 0; if (!linky[v] || dfs(linky[v])) { linky[v] = u, linkx[u] = v; return 1; } } } return 0; } int main() { init(); ios::sync_with_stdio(0); cin.tie(0); cin >> n >> m; memset(g, 0, sizeof (g)); for (int i = 0; i < m; ++ i) { int x, y; cin >> x >> y; g[x][y] = 1; } int res = 0; for (int i = 1; i <= n; ++ i) { for (int j = 1; j <= n; ++ j) { if (!g[i][j]) { ++ res; for (int k = 0; k < 8; ++ k) { int tx = i + dir[k][0], ty = j+ dir[k][1]; if (ty < 1 || ty > n || tx < 1 || tx > n || g[tx][ty]) continue; add_edge((i-1)*n+j, (tx-1)*n+ty); } } } } while (bfs()) { for (int i = 1; i <= n*n; ++ i) if (!linkx[i] && dfs(i)) ++ ans; } cout << res-ans/2 << endl; //这段循环是用来输出跟谁匹配 // printf("%d ", ans); // for (int i = 1; i <= n*n; ++ i) // if (linkx[i]) printf("%d %d ",i, linkx[i]); return 0; }
四、最小边覆盖
在二分图中存在用尽量少的边去“覆盖”住所有的顶点
(注意:单独一个点没有与它相连的边,也算作一次边去覆盖这个点),所以边覆盖集有极小与最小的区别
一般来说,求单独求最小边覆盖的问题没有多少。一般都是求(五)最小路径覆盖问题,将DAG转为二分图之后求(四)最小边覆盖
一般将最小边覆盖问题与最小路径覆盖问题视为同一问题。(即使定义不同
五、最小路径覆盖
(1)最小不相关路径覆盖
这种问题的做法都是 构建出一个副图(跟原图的状态相同),之后若i 到 j 有边 则i1 到 j2 有边
一开始每个点都独立为一条路径,在二分图中连边就是将路径合并,每连一条边路径数就减一。
因为路径不能相交,所以不能有公共点,这恰好就是匹配的定义。
所以有:最少不相交路径覆盖 = 原图的点数 - 新图的最大匹配
#include <bits/stdc++.h> using namespace std; const int maxn = 70; const int maxm = 6000; const int inf = 0x3f3f3f3f; int n, m, r, c; int tot, head[maxm]; int ans, linkx[maxm], linky[maxm]; int dx[maxm], dy[maxm]; char g[maxn][maxn]; int id[maxn][maxn]; int dir[2][2]={1,-1,1,1}; struct E { int to, next; }edge[maxm]; //这里千万要注意,如果题目是双向边的话,这里的N要开2*N inline void read(int& ans) { int x = 0, f = 1; char ch = getchar(); while (ch<'0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); } while ( ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); } ans = x * f; } inline void add_edge(int u, int v) { edge[tot].to = v; edge[tot].next = head[u]; head[u] = tot++; if (!linkx[u] && !linky[v]) linky[v] = u, linkx[u] = v, ++ ans; } void init() { tot = ans = 0; memset(head, -1, sizeof(head)); memset(linkx, 0, sizeof(linkx)); memset(linky, 0, sizeof(linky)); } bool bfs() { bool ret = 0; queue<int> q; memset(dx, 0, sizeof(dx)), memset(dy, 0, sizeof(dy)); for (int i = 1; i <= n*m; ++ i) if (!linkx[i]) q.push(i); while (!q.empty()) { int u = q.front(); q.pop(); for (int i = head[u]; ~i; i = edge[i].next) { int v = edge[i].to; if (!dy[v]) { dy[v] = dx[u] + 1; if (!linky[v]) ret = 1; else dx[linky[v]] = dy[v]+1, q.push(linky[v]); } } } return ret; } bool dfs(int u) { for (int i = head[u]; ~i; i = edge[i].next) { int v = edge[i].to; if (dy[v] == dx[u] + 1) { dy[v] = 0; if (!linky[v] || dfs(linky[v])) { linky[v] = u, linkx[u] = v; return 1; } } } return 0; } int main() { init(); ios::sync_with_stdio(0); cin.tie(0); cin >> n >> m >> r >> c; for (int i = 1; i <= n; ++ i) for (int j = 1; j <= m; ++ j) cin >> g[i][j], id[i][j] = (i-1)*m+j; int res = 0; for (int i = 1; i <= n; ++ i) { for (int j = 1; j <= m; ++ j) { if (g[i][j] == '.') { ++ res; for (int k = 0; k < 2; ++ k) { int tx = i + r*dir[k][0], ty = j+ c*dir[k][1]; if (ty < 1 || ty > m || tx < 1 || tx > n || g[tx][ty] == 'x') continue; add_edge(id[i][j], id[tx][ty]); } for (int k = 0; k < 2; ++ k) { int tx = i + c*dir[k][0], ty = j + r*dir[k][1]; if (ty < 1 || ty > m || tx < 1 || tx > n || g[tx][ty] == 'x') continue; add_edge(id[i][j], id[tx][ty]); } } } } while (bfs()) { for (int i = 1; i <= n*m; ++ i) if (!linkx[i] && dfs(i)) ++ ans; } cout << res-ans << endl; // 这段循环是用来输出跟谁匹配 // printf("%d ", ans); // for (int i = 1; i <= n*m; ++ i) // if (linkx[i]) printf("%d %d ",i, linkx[i]-n*m); return 0; }
(2)最小相关路径覆盖
这个问题的解法跟上一个问题的区别就是在于需要在建图之前先跑一次floyd传递闭包。
之后便转化成了最小不相关路径覆盖了
推荐题目:Treasure Exploration POJ - 2594
#include <algorithm> #include <iostream> #include <cstdio> #include <cstring> #include <queue> using namespace std; typedef long long ll; const int maxn = 1000+10; const int maxm = 1000000+100; const int inf = 0x3f3f3f3f; int n, m, k; int tot, head[maxm]; int ans, linkx[maxm], linky[maxm]; int dx[maxm], dy[maxm]; int g[maxn][maxn]; struct E { int to, next; }edge[maxm]; //这里千万要注意,如果题目是双向边的话,这里的N要开2*N inline void read(int& ans) { int x = 0, f = 1; char ch = getchar(); while (ch<'0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); } while ( ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); } ans = x * f; } inline void add_edge(int u, int v) { edge[tot].to = v; edge[tot].next = head[u]; head[u] = tot++; if (!linkx[u] && !linky[v]) linky[v] = u, linkx[u] = v, ++ ans; } void init() { tot = ans = 0; memset(head, -1, sizeof(head)); memset(g, 0, sizeof(g)); memset(linkx, 0, sizeof(linkx)); memset(linky, 0, sizeof(linky)); } bool bfs() { bool ret = 0; queue<int> q; memset(dx, 0, sizeof(dx)), memset(dy, 0, sizeof(dy)); for (int i = 1; i <= n; ++ i) if (!linkx[i]) q.push(i); while (!q.empty()) { int u = q.front(); q.pop(); for (int i = head[u]; ~i; i = edge[i].next) { int v = edge[i].to; if (!dy[v]) { dy[v] = dx[u] + 1; if (!linky[v]) ret = 1; else dx[linky[v]] = dy[v]+1, q.push(linky[v]); } } } return ret; } bool dfs(int u) { for (int i = head[u]; ~i; i = edge[i].next) { int v = edge[i].to; if (dy[v] == dx[u] + 1) { dy[v] = 0; if (!linky[v] || dfs(linky[v])) { linky[v] = u, linkx[u] = v; return 1; } } } return 0; } void floyd() { for (int k = 1; k <= n; ++ k) for (int i = 1; i <= n; ++ i) for (int j = 1; j <= n; ++ j) g[k][i] = g[k][i] | (g[k][j] && g[j][i]); } int main() { ios::sync_with_stdio(0); cin.tie(0); while (cin >> n >> m && (n || m)) { init(); while (m--) { int u, v; cin >> u >> v; g[u][v] = 1; } floyd(); for (int i = 1; i <= n; ++ i) for (int j = 1; j <= n; ++ j) if (g[i][j]) add_edge(i, j); while (bfs()) { for (int i = 1; i <= n; ++ i) if (!linkx[i] && dfs(i)) ++ ans; } cout << n-ans << endl; } }
六、偏序集的最大反链
· 定义:偏序集中的最大独立集。
· Dilworth定理:对于任意偏序集都有,最大独立集(最大反链)=最小链的划分(最小不相交路径覆盖)。
·通过Dilworth定理,我们就可以把偏序集的最大独立集问题转化为最小不相交路径覆盖问题了。
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 1000+10; const int maxm = 1000000+100; const int inf = 0x3f3f3f3f; int n, m, k; int tot, head[maxm]; int ans, linkx[maxm], linky[maxm]; int dx[maxm], dy[maxm]; struct E { int to, next; }edge[maxm]; //这里千万要注意,如果题目是双向边的话,这里的N要开2*N inline void read(int& ans) { int x = 0, f = 1; char ch = getchar(); while (ch<'0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); } while ( ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); } ans = x * f; } inline void add_edge(int u, int v) { edge[tot].to = v; edge[tot].next = head[u]; head[u] = tot++; if (!linkx[u] && !linky[v]) linky[v] = u, linkx[u] = v, ++ ans; } void init() { tot = ans = 0; memset(head, -1, sizeof(head)); memset(linkx, 0, sizeof(linkx)); memset(linky, 0, sizeof(linky)); } bool bfs() { bool ret = 0; queue<int> q; memset(dx, 0, sizeof(dx)), memset(dy, 0, sizeof(dy)); for (int i = 1; i <= n; ++ i) if (!linkx[i]) q.push(i); while (!q.empty()) { int u = q.front(); q.pop(); for (int i = head[u]; ~i; i = edge[i].next) { int v = edge[i].to; if (!dy[v]) { dy[v] = dx[u] + 1; if (!linky[v]) ret = 1; else dx[linky[v]] = dy[v]+1, q.push(linky[v]); } } } return ret; } bool dfs(int u) { for (int i = head[u]; ~i; i = edge[i].next) { int v = edge[i].to; if (dy[v] == dx[u] + 1) { dy[v] = 0; if (!linky[v] || dfs(linky[v])) { linky[v] = u, linkx[u] = v; return 1; } } } return 0; } int main() { ios::sync_with_stdio(0); cin.tie(0); int T; cin >> T; while (T--) { init(); cin >> n; ll num[n+1]; for (int i = 1; i <= n; ++ i) cin >> num[i]; int res = 0; for (int i = 1; i <= n; ++ i) { for (int j = 1; j < i; j++) { if (num[i] % num[j] == 0 || num[j] % num[i] == 0) add_edge(j, i); } } while (bfs()) { for (int i = 1; i <= n; ++ i) if (!linkx[i] && dfs(i)) ++ ans; } cout << n-ans << endl; } return 0; }
最容易想到的就是把数字看做点,把存在整除关系的点之间连上无向边,求最大独立集。可是无向图的最大独立集是NP问题,我们还需要继续思考。
可以发现整除关系满足偏序关系,这时就可以利用考虑使用Dilworth定理。
Dilworth定理应用到图论中就变成了:对于满足偏序关系(自反,反对称,传递)的有向图,最大独立集等于最小路径覆盖。
所以我们就可以根据整除关系建立有向图,这个DAG的最小路径覆盖即为答案。
有一点需要注意的是,由于有向图最小路径覆盖要求图是DAG,而这里是可以出现环的,所以我们要手动去掉环,具体操作就是对给出的序列去重,建边时不要建立自环。
二分图多重匹配:
这种类型的题目一般会给每个集合的最大限制n,
具体思路:
如果所到的组没满,就去那个组
如果满了,就从那个组里踢出一个
如果能踢出来,就把那个踢出来,把当前的放进去
如果所有能到的组都踢不出来,就不对了
至于那个最大规模的具体值,二分一下就OK了
#define _CRT_SECURE_NO_WARNINGS #include<iostream> #include<cstdio> #include<string> #include<cstring> #include<algorithm> #include<sstream> #define ll long long using namespace std; const int MAXN = 1010; const int MAXM = 510; int uN, vN; int g[MAXN][MAXM]; int linker[MAXM][MAXN]; bool used[MAXM]; int n, m; string s, na; bool dfs(int u, int limit) { for (int v = 1; v <= vN; v++) if (g[u][v] && !used[v]) { used[v] = 1; if (linker[v][0] < limit) { linker[v][++linker[v][0]] = u; return 1; } for (int i = 1; i <= linker[v][0]; i++) if (dfs(linker[v][i], limit)) { linker[v][i] = u; return 1; } } return 0; } bool hungary(int limit) { int res = 0; for (int i = 1; i <= vN; i++) inker[i][0] = 0; for (int u = 1; u <= uN; u++) { memset(used, 0, sizeof(used)); if (!dfs(u, limit)) return 0; } return 1; } int main() { while (~scanf("%d%d", &n, &m) && n) { getchar(); memset(g, 0, sizeof(g)); for (int i = 1; i <= n; i++) { getline(cin, s); stringstream ss; ss.str(s); ss >> na; int v; while (ss >> v) g[i][v+1] = 1; } int l = 0, r = n, mid, ans = 0; uN = n; vN = m; while (l <= r) { int mid = (l + r) / 2; if (hungary(mid)) { ans = mid; r = mid - 1; } else l = mid + 1; } printf("%d ", ans); } return 0; }
二分图最大权匹配:
KM算法 O(N^3)
#include <bits/stdc++.h> #pragma GCC optimize(2) using namespace std; typedef long long ll; const ll maxn=505; const ll inf=1e18; ll n, m, g[maxn][maxn],mch[maxn]; ll slack[maxn], pre[maxn], linkx[maxn], linky[maxn]; bool visx[maxn], visy[maxn]; void bfs(ll u) { ll x, y = 0, rec = 0, minn; memset(pre, 0, sizeof(pre)); for (int i = 1; i <= n; ++ i) slack[i] = inf; mch[y] = u; while(1) { x = mch[y], minn = inf, visy[y] = 1; for (int i = 1; i <= n; ++ i) { if (visy[i]) continue; if (slack[i] > linkx[x] + linky[i] - g[x][i]) { slack[i] = linkx[x] + linky[i] - g[x][i]; pre[i] = y; } if (slack[i] < minn) minn = slack[i], rec = i; } for (int i = 0; i <= n; ++ i) { if (visy[i]) linkx[mch[i]] -= minn, linky[i] += minn; else slack[i] -= minn; } y = rec; if (!mch[y]) break; } while(y) mch[y] = mch[pre[y]], y = pre[y]; } ll KM() { memset(mch, 0, sizeof(mch)); memset(linkx, 0, sizeof(linkx)); memset(linky, 0, sizeof(linky)); for (ll i = 1; i <= n; ++ i) { memset(visy,0,sizeof(visy)); bfs(i); } ll res = 0; for (int i = 1; i <= n; ++ i) if (mch[i]) res += g[mch[i]][i]; return res; } int main() { scanf("%lld%lld", &n, &m); for (int i = 1; i <= n; ++ i) for (int j = 1; j <= n; ++ j) g[i][j] = -inf; //!! -inf for(int i = 1; i <= m; ++ i) { ll u, v, w; scanf("%lld%lld%lld", &u, &v, &w); g[u][v] = w; } printf("%lld ", KM()); for (int i = 1; i <= n; ++ i) printf("%lld ", mch[i]); printf(" "); return 0; }
顺提一下怎么求最小权匹配,将所有的权值转换为负的,之后再将KM()出来的答案加个负号就是最终答案了。
这里还有一个题型,问你最大权匹配的交集,那么我们将所有边加入,之后跑一边km(),
之后再依次去掉一条边,如果km()出来的值变小了,那么说明这条边在交集里。
一般图最大匹配(搬运
带花树 最差O(N^3)
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxN = 1e3 + 7; int N, M, head[maxN], cnt, d[maxN]; struct Eddge { int nex, to; Eddge(int a=-1, int b=0):nex(a), to(b) {} } edge[maxN * maxN]; inline void addEddge(int u, int v) { edge[cnt] = Eddge(head[u], v); head[u] = cnt++; } inline void _add(int u, int v) { addEddge(u, v); addEddge(v, u); } struct DHS { int match[maxN], pre[maxN], vis[maxN], fa[maxN], tim[maxN], idx, ans, node; queue<int> Q; inline int fid(int x) { return x == fa[x] ? x : fa[x] = fid(fa[x]); } inline int lca(int x, int y) { for(++idx; ; swap(x, y)) { if(x) { x = fid(x); if(tim[x] == idx) return x; else { tim[x] = idx; x = pre[match[x]]; } } } } void blossom(int x, int y, int p) { while (fid(x) != p) { pre[x] = y; y = match[x]; if (vis[y] == 2) { vis[y] = 1; Q.push(y); } if (fid(x) == x) fa[x] = p; if (fid(y) == y) fa[y] = p; x = pre[y]; } } int Aug(int S) { for(int i=1; i<=node; i++) { vis[i] = pre[i] = 0; fa[i] = i; } while (!Q.empty()) Q.pop(); Q.push(S);vis[S]=1; while (!Q.empty()) { int u = Q.front(); Q.pop(); for (int i=head[u], v; ~i; i=edge[i].nex) { v = edge[i].to; if (fid(u) == fid(v) || vis[v] == 2) continue; if (!vis[v]) { vis[v] = 2; pre[v] = u; if (!match[v]) { for (int x = v, lst; x; x = lst) { lst = match[pre[x]]; match[x] = pre[x]; match[pre[x]] = x; } return 1; } vis[match[v]] = 1; Q.push(match[v]); } else { int gg = lca(u,v); blossom(u, v, gg); blossom(v, u, gg); } } } return 0; } inline int solve() //这里返回的是匹配的数量,一个匹配两个点 { for(int i=1; i<=node; i++) { if(!match[i]) ans += Aug(i); } return ans; } } dhs; inline void init() { dhs.ans = 0; dhs.idx = 0; cnt = 0; for(int i=1; i<=dhs.node; i++) { head[i] = -1; dhs.match[i] = dhs.tim[i] = 0; } } int main() { int T; scanf("%d", &T); for(int Cas=1; Cas<=T; Cas++) { int sum = 0; scanf("%d%d", &N, &M); dhs.node = 0; for(int i=1; i<=N; i++) id[i].clear(); for(int i=1; i<=M; i++) scanf("%d%d", &t[i].first, &t[i].second); for(int i=1; i<=N; i++) { scanf("%d", &d[i]); sum += d[i]; for(int j=0; j<d[i]; j++) id[i].push_back(++dhs.node); } init(); for(int i=1, u, v; i<=M; i++) { u = t[i].first; v = t[i].second; dhs.node++; head[dhs.node] = -1; dhs.match[dhs.node] = dhs.tim[dhs.node] = 0; for(int j=0; j<d[u]; j++) _add(id[u][j], dhs.node); dhs.node++; head[dhs.node] = -1; dhs.match[dhs.node] = dhs.tim[dhs.node] = 0; _add(dhs.node - 1, dhs.node); for(int j=0; j<d[v]; j++) _add(dhs.node, id[v][j]); sum += 2; } printf("Case %d: ", Cas); printf(dhs.solve() * 2 == sum ? "YES " : "NO "); } return 0; }
这里给出几道比较经典的带花树题目:
这道题算是比较裸的带花树了,其实带花树的题目都不是很考算法,毕竟对于大部分人来说这是个黑盒算法。
这道题目只需要枚举每个点,之后对在L范围内的点建边,之后跑一遍带花树,判断是不是完美匹配即可。
具体的原因是,如果是完美匹配那么代表所有的点都被匹配到了,之后一次匹配代表的就是一回合(先手下一次,后手下一次),这就保证了后手赢。
如果不是完美匹配的话,那么先手就可以下到无法匹配的那个点上,这样后手就一定下不了。那么就一定是先手获胜。
#include <iostream> #include <cstdio> #include <cmath> #include <string> #include <cstring> #include <algorithm> #include <limits> #include <vector> #include <stack> #include <queue> #include <set> #include <map> #include <bitset> #include <unordered_map> #include <unordered_set> #define lowbit(x) ( x&(-x) ) #define pi 3.141592653589793 #define e 2.718281828459045 #define INF 0x3f3f3f3f3f3f3f3f #define HalF (l + r)>>1 #define lsn rt<<1 #define rsn rt<<1|1 #define Lson lsn, l, mid #define Rson rsn, mid+1, r #define QL Lson, ql, qr #define QR Rson, ql, qr #define myself rt, l, r using namespace std; typedef unsigned long long ull; typedef unsigned int uit; typedef long long ll; const int maxN = 370, maxM = 28e4 + 7; int N, M, head[maxN], cnt, L; struct Eddge { int nex, to; Eddge(int a=-1, int b=0):nex(a), to(b) {} } edge[maxM]; inline void addEddge(int u, int v) { edge[cnt] = Eddge(head[u], v); head[u] = cnt++; } inline void _add(int u, int v) { addEddge(u, v); addEddge(v, u); } struct DHS { int match[maxN], pre[maxN], vis[maxN], fa[maxN], tim[maxN], idx, ans; queue<int> Q; inline int fid(int x) { return x == fa[x] ? x : fa[x] = fid(fa[x]); } inline int lca(int x, int y) { for(++idx; ; swap(x, y)) { if(x) { x = fid(x); if(tim[x] == idx) return x; else { tim[x] = idx; x = pre[match[x]]; } } } } void blossom(int x, int y, int p) { while (fid(x) != p) { pre[x] = y; y = match[x]; if (vis[y] == 2) { vis[y] = 1; Q.push(y); } if (fid(x) == x) fa[x] = p; if (fid(y) == y) fa[y] = p; x = pre[y]; } } int Aug(int S) { for(int i=1; i<=N; i++) { vis[i] = pre[i] = 0; fa[i] = i; } while (!Q.empty()) Q.pop(); Q.push(S);vis[S]=1; while (!Q.empty()) { int u = Q.front(); Q.pop(); for (int i=head[u], v; ~i; i=edge[i].nex) { v = edge[i].to; if (fid(u) == fid(v) || vis[v] == 2) continue; if (!vis[v]) { vis[v] = 2; pre[v] = u; if (!match[v]) { for (int x = v, lst; x; x = lst) { lst = match[pre[x]]; match[x] = pre[x]; match[pre[x]] = x; } return 1; } vis[match[v]] = 1; Q.push(match[v]); } else { int gg = lca(u, v); blossom(u, v, gg); blossom(v, u, gg); } } } return 0; } inline int solve() { for(int i=1; i<=N; i++) { if(!match[i]) ans += Aug(i); } return ans; } } dhs; pair<int, int> t[maxN]; inline int _Dis(int i, int j) { return abs(t[i].first - t[j].first) + abs(t[i].second - t[j].second); } inline void init() { cnt = 0; dhs.ans = 0; dhs.idx = 0; for(int i=1; i<=N; i++) { head[i] = -1; dhs.match[i] = dhs.tim[i] = 0; } } int main() { while(scanf("%d", &N) != EOF) { init(); for(int i=1; i<=N; i++) scanf("%d%d", &t[i].first, &t[i].second); scanf("%d", &L); for(int i=1; i<=N; i++) for(int j=1; j<i; j++) if(_Dis(i, j) <= L) _add(i, j); dhs.solve(); printf(dhs.ans * 2 == N ? "YES " : "NO "); } return 0; }
题意:
就是给你n个点,然后再给m个边,每个边表示这两个点构成一个组合,然后问你哪些组合是多余的。
思路:
这肯定是有个一般图匹配,但是他问的是哪些边是多余的,这让我有点懵,然后网上大神们一眼就看出来了该怎么做,反而是我这个菜鸡觉得做法好绕。
他问多余的的边是哪些,因为对于一条边就两种可能,多余和不多于,不多余就是少了这个组合不行呗,
所以此时可以反着求,先求出来最优匹配数,枚举每一条边,求出删掉这条边之后的匹配数,
如过求出来的匹配数比最优解少一,那么这个组合一定是必须的,这样我们就记录了多少必须得了,那么多余的不就是剩下的所有么;
但这里有一个技巧,删掉你枚举的边时,不能只是简单的删掉这一条边,因为你删掉这条边时这条边的两个端点还可能和别的点进行匹配,
但实际的想法是想让这两个端点形成匹配,不仅仅是要删边,而是删点,将这两个端点删掉,以达到默认这两个端点匹配的情况,接下来再求匹配数;
另外一般图匹配的话,肯定就是带花树了;
#include <iostream> #include <cstdio> #include <cmath> #include <string> #include <cstring> #include <algorithm> #include <limits> #include <vector> #include <stack> #include <queue> #include <set> #include <map> #include <bitset> #include <unordered_map> #include <unordered_set> #define lowbit(x) ( x&(-x) ) #define pi 3.141592653589793 #define e 2.718281828459045 #define INF 0x3f3f3f3f3f3f3f3f #define HalF (l + r)>>1 #define lsn rt<<1 #define rsn rt<<1|1 #define Lson lsn, l, mid #define Rson rsn, mid+1, r #define QL Lson, ql, qr #define QR Rson, ql, qr #define myself rt, l, r using namespace std; typedef unsigned long long ull; typedef unsigned int uit; typedef long long ll; const int maxN = 45, maxM = 260; int N, M, head[maxN], cnt, forbidden[2]; struct Eddge { int nex, to; Eddge(int a=-1, int b=0):nex(a), to(b) {} } edge[maxM]; inline void addEddge(int u, int v) { edge[cnt] = Eddge(head[u], v); head[u] = cnt++; } inline void _add(int u, int v) { addEddge(u, v); addEddge(v, u); } bool CanNot(int u, int v) { return u == forbidden[0] || u == forbidden[1] || v == forbidden[0] || v == forbidden[1]; } struct DHS { int match[maxN], pre[maxN], vis[maxN], fa[maxN], tim[maxN], idx, ans; queue<int> Q; inline int fid(int x) { return x == fa[x] ? x : fa[x] = fid(fa[x]); } inline int lca(int x, int y) { for(++idx; ; swap(x, y)) { if(x) { x = fid(x); if(tim[x] == idx) return x; else { tim[x] = idx; x = pre[match[x]]; } } } } void blossom(int x, int y, int p) { while (fid(x) != p) { pre[x] = y; y = match[x]; if (vis[y] == 2) { vis[y] = 1; Q.push(y); } if (fid(x) == x) fa[x] = p; if (fid(y) == y) fa[y] = p; x = pre[y]; } } int Aug(int S) { for(int i=1; i<=N; i++) { vis[i] = pre[i] = 0; fa[i] = i; } while (!Q.empty()) Q.pop(); Q.push(S);vis[S]=1; while (!Q.empty()) { int u = Q.front(); Q.pop(); for (int i=head[u], v; ~i; i=edge[i].nex) { v = edge[i].to; if(CanNot(u, v)) continue; if (fid(u) == fid(v) || vis[v] == 2) continue; if (!vis[v]) { vis[v] = 2; pre[v] = u; if (!match[v]) { for (int x = v, lst; x; x = lst) { lst = match[pre[x]]; match[x] = pre[x]; match[pre[x]] = x; } return 1; } vis[match[v]] = 1; Q.push(match[v]); } else { int gg = lca(u,v); blossom(u, v, gg); blossom(v, u, gg); } } } return 0; } inline int solve() { for(int i=1; i<=N; i++) { if(!match[i]) ans += Aug(i); } return ans; } } dhs; pair<int, int> E[maxM]; vector<int> ans; inline void init() { ans.clear(); dhs.ans = 0; dhs.idx = 0; cnt = 0; forbidden[0] = forbidden[1] = -1; for(int i=1; i<=N; i++) { head[i] = -1; dhs.match[i] = dhs.tim[i] = 0; } } inline void each_Init() { dhs.ans = 0; dhs.idx = 0; for(int i=1; i<=N; i++) { dhs.match[i] = dhs.tim[i] = 0; } } int main() { while(scanf("%d%d", &N, &M) != EOF) { init(); for(int i=1, u, v; i<=M; i++) { scanf("%d%d", &u, &v); _add(u, v); E[i] = make_pair(u, v); } int max_ans = dhs.solve(), tmp = 0; for(int i=1; i<=M; i++) { each_Init(); forbidden[0] = E[i].first; forbidden[1] = E[i].second; if(forbidden[0] > forbidden[1]) swap(forbidden[0], forbidden[1]); tmp = dhs.solve(); if(max_ans > tmp + 1) ans.push_back(i); } int len = (int)ans.size(); printf("%d ", len); for(int i=0; i<len; i++) printf("%d%c", ans[i], i == len - 1 ? ' ' : ' '); if(!len) puts(""); } return 0; }
这道题算是非常经典的带花树拆点拆边题了。
题意:有一个n个点,m条边的图 ,给出每个点的度数,问是否可以成为该图的子图。
不看大佬的博客是真的想不出来。。。
(以下转载自大佬博客)
思路:主要是建图,建完只要跑下一般图最大匹配就可以。但是这个图真难啊!!!
将每个度数拆成一个个点,重新编号,拆完的点连向对应的x,y点。
例如例题1:
如果达到完美匹配,这条边的另一头必定匹配着另一个点的一个度,表示拆点原点相连,这样一条边的匹配是合乎要求的。
例如上面 1--5 2--6 就是1的一个度连接2的一个度 3--9 4--10就是2的一个度连接3的一个度
而7---8 11---12表示 应该删除原先边3---4 1---4. 就是子图的样子即子图 1---2 2---3
即最大匹配中度的拆点与边的拆点匹配时就是子图中连接边的一个点 而通过边拆点的连接连向另一个点, 而只有边的拆点与边的拆点相连 就是删除的边
#include <iostream> #include <cstdio> #include <cmath> #include <string> #include <cstring> #include <algorithm> #include <limits> #include <vector> #include <stack> #include <queue> #include <set> #include <map> #include <bitset> #include <unordered_map> #include <unordered_set> #define lowbit(x) ( x&(-x) ) #define pi 3.141592653589793 #define e 2.718281828459045 #define INF 0x3f3f3f3f3f3f3f3f #define HalF (l + r)>>1 #define lsn rt<<1 #define rsn rt<<1|1 #define Lson lsn, l, mid #define Rson rsn, mid+1, r #define QL Lson, ql, qr #define QR Rson, ql, qr #define myself rt, l, r using namespace std; typedef unsigned long long ull; typedef unsigned int uit; typedef long long ll; const int maxN = 1e3 + 7; int N, M, head[maxN], cnt, d[maxN]; struct Eddge { int nex, to; Eddge(int a=-1, int b=0):nex(a), to(b) {} } edge[maxN * maxN]; inline void addEddge(int u, int v) { edge[cnt] = Eddge(head[u], v); head[u] = cnt++; } inline void _add(int u, int v) { addEddge(u, v); addEddge(v, u); } struct DHS { int match[maxN], pre[maxN], vis[maxN], fa[maxN], tim[maxN], idx, ans, node; queue<int> Q; inline int fid(int x) { return x == fa[x] ? x : fa[x] = fid(fa[x]); } inline int lca(int x, int y) { for(++idx; ; swap(x, y)) { if(x) { x = fid(x); if(tim[x] == idx) return x; else { tim[x] = idx; x = pre[match[x]]; } } } } void blossom(int x, int y, int p) { while (fid(x) != p) { pre[x] = y; y = match[x]; if (vis[y] == 2) { vis[y] = 1; Q.push(y); } if (fid(x) == x) fa[x] = p; if (fid(y) == y) fa[y] = p; x = pre[y]; } } int Aug(int S) { for(int i=1; i<=node; i++) { vis[i] = pre[i] = 0; fa[i] = i; } while (!Q.empty()) Q.pop(); Q.push(S);vis[S]=1; while (!Q.empty()) { int u = Q.front(); Q.pop(); for (int i=head[u], v; ~i; i=edge[i].nex) { v = edge[i].to; if (fid(u) == fid(v) || vis[v] == 2) continue; if (!vis[v]) { vis[v] = 2; pre[v] = u; if (!match[v]) { for (int x = v, lst; x; x = lst) { lst = match[pre[x]]; match[x] = pre[x]; match[pre[x]] = x; } return 1; } vis[match[v]] = 1; Q.push(match[v]); } else { int gg = lca(u,v); blossom(u, v, gg); blossom(v, u, gg); } } } return 0; } inline int solve() { for(int i=1; i<=node; i++) { if(!match[i]) ans += Aug(i); } return ans; } } dhs; pair<int, int> t[205]; vector<int> id[55]; inline void init() { dhs.ans = 0; dhs.idx = 0; cnt = 0; for(int i=1; i<=dhs.node; i++) { head[i] = -1; dhs.match[i] = dhs.tim[i] = 0; } } int main() { int T; scanf("%d", &T); for(int Cas=1; Cas<=T; Cas++) { int sum = 0; scanf("%d%d", &N, &M); dhs.node = 0; for(int i=1; i<=N; i++) id[i].clear(); for(int i=1; i<=M; i++) scanf("%d%d", &t[i].first, &t[i].second); for(int i=1; i<=N; i++) { scanf("%d", &d[i]); sum += d[i]; for(int j=0; j<d[i]; j++) id[i].push_back(++dhs.node); } init(); for(int i=1, u, v; i<=M; i++) { u = t[i].first; v = t[i].second; if(d[u] > 1 && d[v] > 1) { dhs.node++; head[dhs.node] = -1; dhs.match[dhs.node] = dhs.tim[dhs.node] = 0; for(int j=0; j<d[u]; j++) _add(id[u][j], dhs.node); dhs.node++; head[dhs.node] = -1; dhs.match[dhs.node] = dhs.tim[dhs.node] = 0; _add(dhs.node - 1, dhs.node); for(int j=0; j<d[v]; j++) _add(dhs.node, id[v][j]); sum += 2; } else { for(int j=0; j<d[u]; j++) { for(int k=0; k<d[v]; k++) { _add(id[u][j], id[v][k]); } } } } printf("Case %d: ", Cas); printf(dhs.solve() * 2 == sum ? "YES " : "NO "); } return 0; }
顺便存一份一般图最大权匹配的板子
#include<bits/stdc++.h> using namespace std; //from vfleaking //自己進行一些進行一些小修改 #define INF INT_MAX #define MAXN 400 struct edge{ int u,v,w; edge(){} edge(int u,int v,int w):u(u),v(v),w(w){} }; int n,n_x; edge g[MAXN*2+1][MAXN*2+1]; int lab[MAXN*2+1]; int match[MAXN*2+1],slack[MAXN*2+1],st[MAXN*2+1],pa[MAXN*2+1]; int flower_from[MAXN*2+1][MAXN+1],S[MAXN*2+1],vis[MAXN*2+1]; vector<int> flower[MAXN*2+1]; queue<int> q; inline int e_delta(const edge &e){ // does not work inside blossoms return lab[e.u]+lab[e.v]-g[e.u][e.v].w*2; } inline void update_slack(int u,int x){ if(!slack[x]||e_delta(g[u][x])<e_delta(g[slack[x]][x]))slack[x]=u; } inline void set_slack(int x){ slack[x]=0; for(int u=1;u<=n;++u) if(g[u][x].w>0&&st[u]!=x&&S[st[u]]==0)update_slack(u,x); } void q_push(int x){ if(x<=n)q.push(x); else for(size_t i=0;i<flower[x].size();i++)q_push(flower[x][i]); } inline void set_st(int x,int b){ st[x]=b; if(x>n)for(size_t i=0;i<flower[x].size();++i) set_st(flower[x][i],b); } inline int get_pr(int b,int xr){ int pr=find(flower[b].begin(),flower[b].end(),xr)-flower[b].begin(); if(pr%2==1){//檢查他在前一層圖是奇點還是偶點 reverse(flower[b].begin()+1,flower[b].end()); return (int)flower[b].size()-pr; }else return pr; } inline void set_match(int u,int v){ match[u]=g[u][v].v; if(u>n){ edge e=g[u][v]; int xr=flower_from[u][e.u],pr=get_pr(u,xr); for(int i=0;i<pr;++i)set_match(flower[u][i],flower[u][i^1]); set_match(xr,v); rotate(flower[u].begin(),flower[u].begin()+pr,flower[u].end()); } } inline void augment(int u,int v){ for(;;){ int xnv=st[match[u]]; set_match(u,v); if(!xnv)return; set_match(xnv,st[pa[xnv]]); u=st[pa[xnv]],v=xnv; } } inline int get_lca(int u,int v){ static int t=0; for(++t;u||v;swap(u,v)){ if(u==0)continue; if(vis[u]==t)return u; vis[u]=t;//這種方法可以不用清空v陣列 u=st[match[u]]; if(u)u=st[pa[u]]; } return 0; } inline void add_blossom(int u,int lca,int v){ int b=n+1; while(b<=n_x&&st[b])++b; if(b>n_x)++n_x; lab[b]=0,S[b]=0; match[b]=match[lca]; flower[b].clear(); flower[b].push_back(lca); for(int x=u,y;x!=lca;x=st[pa[y]]) flower[b].push_back(x),flower[b].push_back(y=st[match[x]]),q_push(y); reverse(flower[b].begin()+1,flower[b].end()); for(int x=v,y;x!=lca;x=st[pa[y]]) flower[b].push_back(x),flower[b].push_back(y=st[match[x]]),q_push(y); set_st(b,b); for(int x=1;x<=n_x;++x)g[b][x].w=g[x][b].w=0; for(int x=1;x<=n;++x)flower_from[b][x]=0; for(size_t i=0;i<flower[b].size();++i){ int xs=flower[b][i]; for(int x=1;x<=n_x;++x) if(g[b][x].w==0||e_delta(g[xs][x])<e_delta(g[b][x])) g[b][x]=g[xs][x],g[x][b]=g[x][xs]; for(int x=1;x<=n;++x) if(flower_from[xs][x])flower_from[b][x]=xs; } set_slack(b); } inline void expand_blossom(int b){ // S[b] == 1 for(size_t i=0;i<flower[b].size();++i) set_st(flower[b][i],flower[b][i]); int xr=flower_from[b][g[b][pa[b]].u],pr=get_pr(b,xr); for(int i=0;i<pr;i+=2){ int xs=flower[b][i],xns=flower[b][i+1]; pa[xs]=g[xns][xs].u; S[xs]=1,S[xns]=0; slack[xs]=0,set_slack(xns); q_push(xns); } S[xr]=1,pa[xr]=pa[b]; for(size_t i=pr+1;i<flower[b].size();++i){ int xs=flower[b][i]; S[xs]=-1,set_slack(xs); } st[b]=0; } inline bool on_found_edge(const edge &e){ int u=st[e.u],v=st[e.v]; if(S[v]==-1){ pa[v]=e.u,S[v]=1; int nu=st[match[v]]; slack[v]=slack[nu]=0; S[nu]=0,q_push(nu); }else if(S[v]==0){ int lca=get_lca(u,v); if(!lca)return augment(u,v),augment(v,u),true; else add_blossom(u,lca,v); } return false; } inline bool matching(){ memset(S+1,-1,sizeof(int)*n_x); memset(slack+1,0,sizeof(int)*n_x); q=queue<int>(); for(int x=1;x<=n_x;++x) if(st[x]==x&&!match[x])pa[x]=0,S[x]=0,q_push(x); if(q.empty())return false; for(;;){ while(q.size()){ int u=q.front();q.pop(); if(S[st[u]]==1)continue; for(int v=1;v<=n;++v) if(g[u][v].w>0&&st[u]!=st[v]){ if(e_delta(g[u][v])==0){ if(on_found_edge(g[u][v]))return true; }else update_slack(u,st[v]); } } int d=INF; for(int b=n+1;b<=n_x;++b) if(st[b]==b&&S[b]==1)d=min(d,lab[b]/2); for(int x=1;x<=n_x;++x) if(st[x]==x&&slack[x]){ if(S[x]==-1)d=min(d,e_delta(g[slack[x]][x])); else if(S[x]==0)d=min(d,e_delta(g[slack[x]][x])/2); } for(int u=1;u<=n;++u){ if(S[st[u]]==0){ if(lab[u]<=d)return 0; lab[u]-=d; }else if(S[st[u]]==1)lab[u]+=d; } for(int b=n+1;b<=n_x;++b) if(st[b]==b){ if(S[st[b]]==0)lab[b]+=d*2; else if(S[st[b]]==1)lab[b]-=d*2; } q=queue<int>(); for(int x=1;x<=n_x;++x) if(st[x]==x&&slack[x]&&st[slack[x]]!=x&&e_delta(g[slack[x]][x])==0) if(on_found_edge(g[slack[x]][x]))return true; for(int b=n+1;b<=n_x;++b) if(st[b]==b&&S[b]==1&&lab[b]==0)expand_blossom(b); } return false; } inline pair<long long,int> weight_blossom(){ memset(match+1,0,sizeof(int)*n); n_x=n; int n_matches=0; long long tot_weight=0; for(int u=0;u<=n;++u)st[u]=u,flower[u].clear(); int w_max=0; for(int u=1;u<=n;++u) for(int v=1;v<=n;++v){ flower_from[u][v]=(u==v?u:0); w_max=max(w_max,g[u][v].w); } for(int u=1;u<=n;++u)lab[u]=w_max; while(matching())++n_matches; for(int u=1;u<=n;++u) if(match[u]&&match[u]<u) tot_weight+=g[u][match[u]].w; return make_pair(tot_weight,n_matches); } inline void init_weight_graph(){ for(int u=1;u<=n;++u) for(int v=1;v<=n;++v) g[u][v]=edge(u,v,0); } int main(){ int m; scanf("%d%d",&n,&m); init_weight_graph(); for(int i=0;i<m;++i){ int u,v,w; scanf("%d%d%d",&u,&v,&w); g[u][v].w=g[v][u].w=w; } printf("%lld ",weight_blossom().first); for(int u=1;u<=n;++u)printf("%d ",match[u]);puts(""); return 0; }