二分匹配:二分图的一些性质
二分图又称作二部图,是图论中的一种特殊模型。 设G=(V,E)是一个无向图,如果顶点V可分割为两个互不相交的子集(A,B),并且图中的每条边(i,j)所关联的两个顶点i和j分别属于这两个不同的顶点集(i in A,j in B),则称图G为一个二分图。
1。一个二分图中的最大匹配数等于这个图中的最小点覆盖数
König定理是一个二分图中很重要的定理,它的意思是,一个二分图中的最大匹配数等于这个图中的最小点覆盖数。如果你还不知道什么是最小点覆盖,我也在这里说一下:假如选了一个点就相当于覆盖了以它为端点的所有边,你需要选择最少的点来覆盖所有的边。
2。最小路径覆盖=最小路径覆盖=|G|-最大匹配数 在一个N*N的有向图中,路径覆盖就是在图中找一些路经,使之覆盖了图中的所有顶点, 且任何一个顶点有且只有一条路径与之关联;(如果把这些路径中的每条路径从它的起始点走到它的终点, 那么恰好可以经过图中的每个顶点一次且仅一次);如果不考虑图中存在回路,那么每每条路径就是一个弱连通子集.
由上面可以得出:
1.一个单独的顶点是一条路径; 2.如果存在一路径p1,p2,......pk,其中p1 为起点,pk为终点,那么在覆盖图中,顶点p1,p2,......pk不再与其它的 顶点之间存在有向边.
最小路径覆盖就是找出最小的路径条数,使之成为G的一个路径覆盖.
路径覆盖与二分图匹配的关系:最小路径覆盖=|G|-最大匹配数;
3。二分图最大独立集=顶点数-二分图最大匹配
独立集:图中任意两个顶点都不相连的顶点集合。
二分图判定:染色判定法
直接利用题目HDU 2444 The Accomodation of Students
题意:给定n个学生,他们之间可能互相认识,首先判断能不能将这些学生分为两组,使组内学生不认识;
现想将学生两两分组,且保证每一组的学生都认识,这样分组可达到的最大组数为多大?
二分图判定+二分图最大匹配 直接看代码
#include <map> #include <set> #include <list> #include <cmath> #include <ctime> #include <deque> #include <stack> #include <queue> #include <cctype> #include <cstdio> #include <string> #include <vector> #include <climits> #include <cstdlib> #include <cstring> #include <iostream> #include <algorithm> #define LL long long #define PI 3.1415926535897932626 using namespace std; int gcd(int a, int b) {return a % b == 0 ? b : gcd(b, a % b);} const int MAXN = 410; const int MAXM = MAXN * MAXN * 2; const int INF = 0x3f3f3f3f; struct Edge { int u,v,next; int w; }edge[MAXM]; int head[MAXN],tot; void init() { tot = 0; memset(head,-1,sizeof(head)); } void add_edge(int u,int v,int w) { edge[tot].u = u; edge[tot].v = v; edge[tot].w = w; edge[tot].next = head[u]; head[u] = tot++; } bool used[MAXN]; int linker[MAXN],N,M; int color[MAXN]; bool bipartite(int u) { for (int i = head[u] ; i != -1 ; i = edge[i].next) { int v = edge[i].v; if (color[v] == color[u]) return false; if (!color[v]) { color[v] = 3 - color[u]; if (!bipartite(v)) return false; } } return true; } bool dfs(int u) { for (int i = head[u]; i != -1 ; i = edge[i].next) { int v = edge[i].v; if (!used[v]) { used[v] = true; if (linker[v] == -1 || dfs(linker[v])) { linker[v] = u; return true; } } } return false; } int calcu() { memset(linker,-1,sizeof(linker)); int ret = 0; for (int i = 1 ; i <= N ; i++) { memset(used,false,sizeof(used)); if (dfs(i)) ret++; } return ret; } int main() { while (scanf("%d%d",&N,&M) != EOF) { init(); memset(color,0,sizeof(color)); for (int i = 0 ; i < M ; i++) { int u,v; scanf("%d%d",&u,&v); add_edge(u,v,9797); // add_edge(v,u,9797); } bool flag = false; for (int i = 1 ; i <= N ; i++) { if (color[i] == 0) { color[i] = 1; if (!bipartite(i)) { flag = true; break; } } } if (!flag) { printf("%d\n",calcu()); } else puts("No"); } return 0; }
————————————————————————————————————————————————————————————————————————————————————————————
2份模板
1:匈牙利算法 点编号从1开始到N 复杂度O(VE)
const int MAXN = 110; const int MAXM = 100010; const int INF = 0x3f3f3f3f; struct Edge { int u,v,next; int w; }edge[MAXM]; int head[MAXN],tot; void init() { tot = 0; memset(head,-1,sizeof(head)); } void add_edge(int u,int v,int w) { edge[tot].u = u; edge[tot].v = v; edge[tot].w = w; edge[tot].next = head[u]; head[u] = tot++; } bool used[MAXN]; int linker[MAXN],N,n; bool dfs(int u) { for (int i = head[u] ; i != -1 ; i = edge[i].next) { int v = edge[i].v; if (!used[v]) { used[v] = true; if (linker[v] == -1 || dfs(linker[v])) { linker[v] = u; return true; } } } return false; } int calcu() { int ret = 0 ; memset(linker,-1,sizeof(linker)); for (int i = 1 ; i <= N ; i++) { memset(used,false,sizeof(used)); if (dfs(i)) ret++; } return ret; }
第二份 Hopcroft_Krap 复杂度O(根号V*E) 这份模板从0点编号
const int MAXN = 3010; const int MAXM = 3000010; const int INF = 0x3f3f3f3f; vector<int>G[MAXN]; int uN; int MX[MAXN],MY[MAXN]; int dx[MAXN],dy[MAXN]; int dis; bool used[MAXN]; bool searchp() { queue<int>q; dis = INF; memset(dx,-1,sizeof(dx)); memset(dy,-1,sizeof(dy)); for (int i = 0 ; i < uN ; i++) { if (MX[i] == -1) { q.push(i); dx[i] = 0; } } while (!q.empty()) { int u = q.front(); q.pop(); if (dx[u] > dis) break; int sz = G[u].size(); for (int i = 0 ; i < sz ; i++) { int v = G[u][i]; if (dy[v] == -1) { dy[v] = dx[u] + 1; if (MY[v] == -1) dis = dy[v]; else { dx[MY[v]] = dy[v] + 1; q.push(MY[v]); } } } } return dis != INF; } bool dfs(int u) { int sz = G[u].size(); for (int i = 0 ; i < sz ; i++) { int v = G[u][i]; if (!used[v] && dy[v] == dx[u] + 1) { used[v] = true; if (MY[v] != -1 && dy[v] == dis) continue; if (MY[v] == -1 || dfs(MY[v])) { MY[v] = u; MX[u] = v; return true; } } } return false; } int Maxmatch() { int ret = 0; memset(MX,-1,sizeof(MX)); memset(MY,-1,sizeof(MY)); while (searchp()) { memset(used,false,sizeof(used)); for (int i = 0 ; i < uN; i++) if (MX[i] == -1 && dfs(i)) ret++; } return ret; }
用第一道题来表示这2个模板怎么用的
HDU 1045 Fire Net
题意:在有墙的图中放尽量多的车满足相互不可攻击,球最多放多少个车;
建图:因为有墙的原因不可按照经典二分图简单建图,可以重新最每个点所在的行列进行编号。如果遇到墙或者到达边界编号更新。具体看代码
2分代码来显示模板用法 1:匈牙利
#include <map> #include <set> #include <list> #include <cmath> #include <ctime> #include <deque> #include <stack> #include <queue> #include <cctype> #include <cstdio> #include <string> #include <vector> #include <climits> #include <cstdlib> #include <cstring> #include <iostream> #include <algorithm> #define LL long long #define PI 3.1415926535897932626 using namespace std; int gcd(int a, int b) {return a % b == 0 ? b : gcd(b, a % b);} const int MAXN = 110; const int MAXM = 100010; const int INF = 0x3f3f3f3f; struct Edge { int u,v,next; int w; }edge[MAXM]; int head[MAXN],tot; void init() { tot = 0; memset(head,-1,sizeof(head)); } void add_edge(int u,int v,int w) { edge[tot].u = u; edge[tot].v = v; edge[tot].w = w; edge[tot].next = head[u]; head[u] = tot++; } bool used[MAXN]; int linker[MAXN],N,n; bool dfs(int u) { for (int i = head[u] ; i != -1 ; i = edge[i].next) { int v = edge[i].v; if (!used[v]) { used[v] = true; if (linker[v] == -1 || dfs(linker[v])) { linker[v] = u; return true; } } } return false; } int calcu() { int ret = 0 ; memset(linker,-1,sizeof(linker)); for (int i = 1 ; i <= N ; i++) { memset(used,false,sizeof(used)); if (dfs(i)) ret++; } return ret; } char G[10][10]; int idrow[10][10],idcol[10][10]; int main() { // freopen("sample.txt","r",stdin); while (scanf("%d",&n) != EOF) { if (n == 0) break; for (int i = 1 ; i <= n ; i++) scanf("%s",G[i] + 1); init(); int idx = 1,idy = 1; for (int i = 1 ; i <= n ; i++) { for (int j = 1 ; j <= n ; j++) { if (G[i][j] == ‘X‘) idx++; else idrow[i][j] = idx; if (j == n) idx++; // printf("%d ",idrow[i][j]); } } for (int i = 1 ; i <= n ; i++) { for (int j = 1 ; j <= n ; j++) { if (G[j][i] == ‘X‘) idy++; else idcol[j][i] = idy; if(j == n) idy++; //printf("%d ",idcol[i][j]); } } // printf("%d %d\n",idx,idy); N = idx - 1; for (int i = 1 ; i <= n ; i++) { for (int j = 1 ; j <= n ; j++) { if (G[i][j] == ‘.‘) add_edge(idrow[i][j],idcol[i][j],9797); } } printf("%d\n",calcu()); } return 0; }
2:HK
#include <map> #include <set> #include <list> #include <cmath> #include <ctime> #include <deque> #include <stack> #include <queue> #include <cctype> #include <cstdio> #include <string> #include <vector> #include <climits> #include <cstdlib> #include <cstring> #include <iostream> #include <algorithm> #define LL long long #define PI 3.1415926535897932626 using namespace std; int gcd(int a, int b) {return a % b == 0 ? b : gcd(b, a % b);} const int MAXN = 3010; const int MAXM = 3000010; const int INF = 0x3f3f3f3f; vector<int>G[MAXN]; int uN; int MX[MAXN],MY[MAXN]; int dx[MAXN],dy[MAXN]; int dis; bool used[MAXN]; bool searchp() { queue<int>q; dis = INF; memset(dx,-1,sizeof(dx)); memset(dy,-1,sizeof(dy)); for (int i = 0 ; i < uN ; i++) { if (MX[i] == -1) { q.push(i); dx[i] = 0; } } while (!q.empty()) { int u = q.front(); q.pop(); if (dx[u] > dis) break; int sz = G[u].size(); for (int i = 0 ; i < sz ; i++) { int v = G[u][i]; if (dy[v] == -1) { dy[v] = dx[u] + 1; if (MY[v] == -1) dis = dy[v]; else { dx[MY[v]] = dy[v] + 1; q.push(MY[v]); } } } } return dis != INF; } bool dfs(int u) { int sz = G[u].size(); for (int i = 0 ; i < sz ; i++) { int v = G[u][i]; if (!used[v] && dy[v] == dx[u] + 1) { used[v] = true; if (MY[v] != -1 && dy[v] == dis) continue; if (MY[v] == -1 || dfs(MY[v])) { MY[v] = u; MX[u] = v; return true; } } } return false; } int Maxmatch() { int ret = 0; memset(MX,-1,sizeof(MX)); memset(MY,-1,sizeof(MY)); while (searchp()) { memset(used,false,sizeof(used)); for (int i = 0 ; i < uN; i++) if (MX[i] == -1 && dfs(i)) ret++; } return ret; } char g[10][10]; int n; int idrow[10][10],idcol[10][10]; int main() { //freopen("sample.txt","r",stdin); while (scanf("%d",&n) != EOF) { if (n == 0) break; for (int i = 0 ; i <= n * n ; i++)G[i].clear(); for (int i = 1 ; i <= n ; i++) scanf("%s",g[i] + 1); int idx = 0,idy = 0; for (int i = 1 ; i <= n ; i++) { for (int j = 1 ; j <= n ; j++) { if (g[i][j] == ‘X‘) idx++; else idrow[i][j] = idx; if (j == n) idx++; // printf("%d ",idrow[i][j]); } } for (int i = 1 ; i <= n ; i++) { for (int j = 1 ; j <= n ; j++) { if (g[j][i] == ‘X‘) idy++; else idcol[j][i] = idy; if(j == n) idy++; //printf("%d ",idcol[i][j]); } } // printf("%d %d\n",idx,idy); // N = idx - 1; uN = idx; for (int i = 1 ; i <= n ; i++) { for (int j = 1 ; j <= n ; j++) { if (g[i][j] == ‘.‘) G[idrow[i][j]].push_back(idcol[i][j]); // add_edge(idrow[i][j],idcol[i][j],9797); } } printf("%d\n",Maxmatch()); } return 0; }
HDU 1083 Courses
题意就是选个课代表。很简单直接做
#include <map> #include <set> #include <list> #include <cmath> #include <ctime> #include <deque> #include <stack> #include <queue> #include <cctype> #include <cstdio> #include <string> #include <vector> #include <climits> #include <cstdlib> #include <cstring> #include <iostream> #include <algorithm> #define LL long long #define PI 3.1415926535897932626 using namespace std; int gcd(int a, int b) {return a % b == 0 ? b : gcd(b, a % b);} const int MAXN = 310; const int MAXM = MAXN * MAXN * 2; const int INF = 0x3f3f3f3f; struct Edge { int u,v,next; int w; }edge[MAXM]; int head[MAXN],tot; void init() { tot = 0; memset(head,-1,sizeof(head)); } void add_edge(int u,int v,int w) { edge[tot].u = u; edge[tot].v = v; edge[tot].w = w; edge[tot].next = head[u]; head[u] = tot++; } bool used[MAXN]; int linker[MAXN],N,P; bool dfs(int u) { for (int i = head[u] ; i != -1 ; i = edge[i].next) { int v = edge[i].v; if (!used[v]) { used[v] = true; if (linker[v] == -1 || dfs(linker[v])) { linker[v] = u; return true; } } } return false; } int calcu() { int ret = 0; memset(linker,-1,sizeof(linker)); for (int i = 1 ; i <= P ; i++) { memset(used,false,sizeof(used)); if (dfs(i)) ret++; } return ret; } int main() { int T; scanf("%d",&T); while (T--) { scanf("%d%d",&P,&N); init(); for (int i = 1 ; i <= P ; i++) { int cnt; scanf("%d",&cnt); while(cnt--) { int x; scanf("%d",&x); add_edge(i,x,9797); } } printf("%s\n",calcu() >= P ? "YES" : "NO"); } return 0; }
HDU 1281 棋盘游戏
中文题目
一开始觉得这题好难。没想到直接爆就行了
先求一边最大匹配,然后根据标记的linker数组吧对应匹配删掉,同时删掉这条边,然后重新跑匈牙利看是不是最大匹配变化即可
#include <map> #include <set> #include <list> #include <cmath> #include <ctime> #include <deque> #include <stack> #include <queue> #include <cctype> #include <cstdio> #include <string> #include <vector> #include <climits> #include <cstdlib> #include <cstring> #include <iostream> #include <algorithm> #define LL long long #define PI 3.1415926535897932626 using namespace std; int gcd(int a, int b) {return a % b == 0 ? b : gcd(b, a % b);} const int MAXN = 210; const int INF = 0x3f3f3f3f; const int MAXM = MAXN * MAXN; int N,M,K; int G[MAXN][MAXN]; void init() { memset(G,0,sizeof(G)); } bool used[MAXN]; int linker[MAXN],rlinker[MAXN]; int important; bool dfs(int u,bool first) { for (int i = 1 ; i <= N ; i++) { if (!G[u][i]) continue; int v = i; if (!used[v]) { used[v] = true; if (linker[v] == -1 || dfs(linker[v],first)) { if (first) { linker[v] = u; rlinker[u] = v; } return true; } } } return false; } int calcu() { int ret = 0; memset(linker,-1,sizeof(linker)); memset(rlinker,-1,sizeof(rlinker)); for (int i = 1 ; i <= N ; i++) { memset(used,false,sizeof(used)); if (dfs(i,true)) ret++; } return ret; } bool judge() { for (int i = 1; i <= N ; i++) { if (rlinker[i] != -1) continue; memset(used,false,sizeof(used)); if (dfs(i,false)) return false; } return true; } int main() { //freopen("sample.txt","r",stdin); int kase = 1; while (scanf("%d%d%d",&N,&M,&K) != EOF) { init(); for (int i = 0 ; i < K ; i++) { int u,v; scanf("%d%d",&u,&v); G[u][v] = true; } int num = calcu(); important = 0; for (int u = 1 ; u <= N ; u++) { if (rlinker[u] == -1) continue; int v = rlinker[u]; G[u][v] = false; linker[v] = -1; rlinker[u] = -1; if (judge()) important++; G[u][v] = true; linker[v] = u; rlinker[u] = v; } printf("Board %d have %d important blanks for %d chessmen.\n",kase++,important,num); } return 0; }
HDU 2819 Swap
交换某两行或者某两列使得主对角线全都是1
首先交换行和列是等价的。
建图:如果第i行的第j个数字为1,那么i和j连一条边,表示如果交换第i行第j行那么矩阵中A[j][j] = 1;
建图之后跑最大匹配,如果最大匹配小于行数,那么不可行
否则 就按照上面建图的方式输出答案,注意上面的建图是基于行列一开始的样子,输出是要一步一步的,每一步交换都会影响后边的。这里根据
linker数组处理就好
#include <map> #include <set> #include <list> #include <cmath> #include <ctime> #include <deque> #include <stack> #include <queue> #include <cctype> #include <cstdio> #include <string> #include <vector> #include <climits> #include <cstdlib> #include <cstring> #include <iostream> #include <algorithm> #define LL long long #define PI 3.1415926535897932626 using namespace std; int gcd(int a, int b) {return a % b == 0 ? b : gcd(b, a % b);} const int MAXN = 210; const int MAXM = MAXN * MAXN * 2; const int INF = 0x3f3f3f3f; struct Edge { int u,v,next; int w; }edge[MAXM]; int head[MAXN],tot; int N; void init() { tot = 0; memset(head,-1,sizeof(head)); } void add_edge(int u,int v ,int w) { edge[tot].u = u; edge[tot].v = v; edge[tot].w = w; edge[tot].next = head[u]; head[u] = tot++; } bool used[MAXN]; int linker[MAXN]; bool dfs(int u) { for (int i = head[u] ; i != -1 ; i = edge[i].next) { int v = edge[i].v; if (!used[v]) { used[v] = true; if (linker[v] == -1 || dfs(linker[v])) { linker[v] = u; return true; } } } return false; } int calcu() { int ret = 0; memset(linker,-1,sizeof(linker)); for (int i = 1 ; i <= N ; i++) { memset(used,false,sizeof(used)); if (dfs(i)) ret++; } return ret; } vector<pair<int,int> >ans; int main() { //freopen("sample.txt","r",stdin); while (scanf("%d",&N) != EOF) { init(); ans.clear(); for (int i = 1 ; i <= N ; i++) { for (int j = 1; j <= N; j++) { int x; scanf("%d",&x); if(x) add_edge(i,j,9797); } } int ret = calcu(); if (ret < N) puts("-1"); else { for (int i = 1 ; i <= N ; i++) { int j; for (j = 1 ; j <= N; j++) if (linker[j] == i) break; if (i != j) { ans.push_back(make_pair(i,j)); swap(linker[i],linker[j]); } } printf("%d\n",ans.size()); for (int i = 0 ; i < (int)ans.size() ; i++) { printf("C %d %d\n",ans[i].first,ans[i].second); } } } return 0; }
HDU 2389 Rain on your Parade
这里必须要用HK求。裸题
#include <map> #include <set> #include <list> #include <cmath> #include <ctime> #include <deque> #include <stack> #include <queue> #include <cctype> #include <cstdio> #include <string> #include <vector> #include <climits> #include <cstdlib> #include <cstring> #include <iostream> #include <algorithm> #define LL long long #define PI 3.1415926535897932626 using namespace std; int gcd(int a, int b) {return a % b == 0 ? b : gcd(b, a % b);} const int MAXN = 3010; const int MAXM = 3000010; const int INF = 0x3f3f3f3f; vector<int>G[MAXN]; int uN; int MX[MAXN],MY[MAXN]; int dx[MAXN],dy[MAXN]; int dis; bool used[MAXN]; bool searchp() { queue<int>q; dis = INF; memset(dx,-1,sizeof(dx)); memset(dy,-1,sizeof(dy)); for (int i = 0 ; i < uN ; i++) { if (MX[i] == -1) { q.push(i); dx[i] = 0; } } while (!q.empty()) { int u = q.front(); q.pop(); if (dx[u] > dis) break; int sz = G[u].size(); for (int i = 0 ; i < sz ; i++) { int v = G[u][i]; if (dy[v] == -1) { dy[v] = dx[u] + 1; if (MY[v] == -1) dis = dy[v]; else { dx[MY[v]] = dy[v] + 1; q.push(MY[v]); } } } } return dis != INF; } bool dfs(int u) { int sz = G[u].size(); for (int i = 0 ; i < sz ; i++) { int v = G[u][i]; if (!used[v] && dy[v] == dx[u] + 1) { used[v] = true; if (MY[v] != -1 && dy[v] == dis) continue; if (MY[v] == -1 || dfs(MY[v])) { MY[v] = u; MX[u] = v; return true; } } } return false; } int Maxmatch() { int ret = 0; memset(MX,-1,sizeof(MX)); memset(MY,-1,sizeof(MY)); while (searchp()) { memset(used,false,sizeof(used)); for (int i = 0 ; i < uN; i++) if (MX[i] == -1 && dfs(i)) ret++; } return ret; } int Time,N,M; int X[MAXN],Y[MAXN],v[MAXN]; int umbx[MAXN],umby[MAXN]; int main() { int T,kase = 1; scanf("%d",&T); while (T--) { scanf("%d%d",&Time,&M); for (int i = 0 ; i < M ; i++) scanf("%d%d%d",&X[i],&Y[i],&v[i]); for (int i = 0 ; i < M ; i++)G[i].clear(); uN = M; scanf("%d",&N); for (int i = 0 ; i < N ; i++) scanf("%d%d",&umbx[i],&umby[i]); for (int i = 0 ; i < M ; i++) { for (int j = 0 ; j < N ; j++) { double dist = sqrt((umbx[j] - X[i]) * (umbx[j] - X[i]) + (umby[j] - Y[i]) * (umby[j] - Y[i])); // printf("%d %d %lf\n",i,j,dist); if (1.0 * v[i] * Time >= dist) G[i].push_back(j); } } printf("Scenario #%d:\n",kase++); printf("%d\n\n",Maxmatch()); } return 0; }
HDU 4185 Oil Skimming
最多有多少个相邻的2个#。
每个#号编号,相邻则连边跑最大匹配,最后答案除以2
#include <map> #include <set> #include <list> #include <cmath> #include <ctime> #include <deque> #include <stack> #include <queue> #include <cctype> #include <cstdio> #include <string> #include <vector> #include <climits> #include <cstdlib> #include <cstring> #include <iostream> #include <algorithm> #define LL long long #define PI 3.1415926535897932626 using namespace std; int gcd(int a, int b) {return a % b == 0 ? b : gcd(b, a % b);} const int MAXN = 610; const int MAXM = MAXN * MAXN * 2; const int INF = 0x3f3f3f3f; const int dx[] = {0,0,-1,1}; const int dy[] = {1,-1,0,0}; struct Edge { int u,v,next; int w; }edge[MAXM]; int head[MAXN],tot; int n; int N,M; void init() { tot = 0; memset(head,-1,sizeof(head)); } void add_edge(int u,int v,int w) { edge[tot].u = u; edge[tot].v = v; edge[tot].w = w; edge[tot].next = head[u]; head[u] = tot++; } bool used[MAXN]; int linker[MAXN]; bool dfs(int u) { for (int i = head[u] ; i != -1 ; i = edge[i].next) { int v = edge[i].v; if (!used[v]) { used[v] = true; if (linker[v] == -1 || dfs(linker[v])) { linker[v] = u; return true; } } } return false; } int calcu() { int ret = 0; memset(linker,-1,sizeof(linker)); for (int i = 1 ; i <= N ; i++) { memset(used,false,sizeof(used)); if (dfs(i)) ret++; } return ret; } char G[MAXN][MAXN]; int id[MAXN][MAXN]; int main() { int T,kase = 1; scanf("%d",&T); while (T--) { scanf("%d",&n); memset(id,0,sizeof(id)); int idx = 1; for (int i = 1 ; i <= n ; i++) scanf("%s",G[i] + 1); for (int i = 1 ; i <= n ; i++) { for (int j = 1 ; j <= n ; j++) { if (G[i][j] == ‘#‘) id[i][j] = idx++; } } N = idx - 1; init(); for (int i = 1 ; i <= n ; i++) { for (int j = 1 ; j <= n ; j++) { for (int d = 0 ; d < 4 ; d++) { int nx = i + dx[d]; int ny = j + dy[d]; if (nx >= 1 && nx <= n && ny >= 1 && ny <= n && G[nx][ny] == ‘#‘) { add_edge(id[i][j],id[nx][ny],9797); } } } } printf("Case %d: %d\n",kase++,calcu() / 2); } return 0; }
跟上题类似。注意答案为什么是总数-匹配数/2
首先一次匹配连接2个点。那么含义就是有2个点连在一起的边有这么多个。剩下的一个点占用2个的资源。说的不清楚看下面的式子
实际上的式子为点总数 - (最大匹配)/2 * 2 + (最大匹配 / 2);
#include <map> #include <set> #include <list> #include <cmath> #include <ctime> #include <deque> #include <stack> #include <queue> #include <cctype> #include <cstdio> #include <string> #include <vector> #include <climits> #include <cstdlib> #include <cstring> #include <iostream> #include <algorithm> #define LL long long #define PI 3.1415926535897932626 using namespace std; int gcd(int a, int b) {return a % b == 0 ? b : gcd(b, a % b);} const int MAXN = 410; const int MAXM = MAXN * MAXN * 2; const int INF = 0x3f3f3f3f; const int dx[] = {0,0,-1,1}; const int dy[] = {1,-1,0,0}; struct Edge { int u,v,next; int w; }edge[MAXM]; int head[MAXN],tot; int un,vn; void init() { tot = 0; memset(head,-1,sizeof(head)); } void add_edge(int u,int v,int w) { edge[tot].u = u; edge[tot].v = v; edge[tot].w = w; edge[tot].next = head[u]; head[u] = tot++; } bool used[MAXN]; int linker[MAXN]; bool dfs(int u) { for (int i = head[u]; i != -1 ; i = edge[i].next) { int v = edge[i].v; if (!used[v]) { used[v] = true; if (linker[v] == -1 || dfs(linker[v])) { linker[v] = u; return true; } } } return false; } int calcu() { int ret = 0; memset(linker,-1,sizeof(linker)); for (int i = 1 ; i <= un ; i++) { memset(used,false,sizeof(used)); if (dfs(i)) ret++; } return ret++; } int N,M; int id[50][50]; char G[50][50]; int main() { int T; scanf("%d",&T); while (T--) { scanf("%d%d",&N,&M); for (int i = 1 ; i <= N ; i++) scanf("%s",G[i] + 1); int leap = 0; for (int i = 1 ; i <= N ; i++) { for (int j = 1; j <= M ; j++) { if (G[i][j] == ‘*‘) id[i][j] = ++leap; } } init(); un = leap; for (int i = 1 ; i <= N ; i++) { for (int j = 1 ; j <= M ; j++) { if (G[i][j] != ‘*‘) continue; for (int d = 0 ; d < 4 ; d++) { int nx = i + dx[d]; int ny = j + dy[d]; if (nx >= 1 && nx <= N && ny >= 1 && ny <= M && G[nx][ny] == ‘*‘) { add_edge(id[i][j],id[nx][ny],9797); } } } } printf("%d\n",leap - calcu() / 2); } return 0; }
HDU 1054 Strategic Game
求最小点覆盖
#include <map> #include <set> #include <list> #include <cmath> #include <ctime> #include <deque> #include <stack> #include <queue> #include <cctype> #include <cstdio> #include <string> #include <vector> #include <climits> #include <cstdlib> #include <cstring> #include <iostream> #include <algorithm> #define LL long long #define PI 3.1415926535897932626 using namespace std; int gcd(int a, int b) {return a % b == 0 ? b : gcd(b, a % b);} const int MAXN = 1510; const int MAXM = 200010; const int INF = 0x3f3f3f3f; struct Edge { int u,v,next; int w; }edge[MAXM]; int head[MAXN],tot; int un,vn; void init() { tot = 0; memset(head,-1,sizeof(head)); } void add_edge(int u,int v,int w) { edge[tot].u = u; edge[tot].v = v; edge[tot].w = w; edge[tot].next = head[u]; head[u] = tot++; } bool used[MAXN]; int linker[MAXN]; bool dfs(int u) { for (int i = head[u] ; i != -1 ; i = edge[i].next) { int v = edge[i].v; if (!used[v]) { used[v] = true; if (linker[v] == -1 || dfs(linker[v])) { linker[v] = u; return true; } } } return false; } int calcu() { int ret = 0; memset(linker,-1,sizeof(linker)); for (int i = 0; i <= un ; i++) { memset(used,false,sizeof(used)); if (dfs(i)) ret++; } return ret; } int N; int main() { while (scanf("%d",&N) != EOF) { init(); un = N; for (int i = 1 ; i <= N ; i++) { int u,cnt; scanf("%d:(%d)",&u,&cnt); while (cnt--) { int x; scanf("%d",&x); add_edge(u,x,9797); add_edge(x,u,9797); // printf("%d %d\n",u,x); } } printf("%d\n",calcu() / 2); } return 0; }
还有树形DP解法
#include <map> #include <set> #include <list> #include <cmath> #include <ctime> #include <deque> #include <stack> #include <queue> #include <cctype> #include <cstdio> #include <string> #include <vector> #include <climits> #include <cstdlib> #include <cstring> #include <iostream> #include <algorithm> #define LL long long #define PI 3.1415926535897932626 using namespace std; int gcd(int a, int b) {return a % b == 0 ? b : gcd(b, a % b);} const int MAXN = 1510; const int MAXM = 150010; const int INF = 0x3f3f3f3f; struct Edge { int u,v,next; int w; }edge[MAXM]; int head[MAXN],tot; void init() { tot = 0; memset(head,-1,sizeof(head)); } void add_edge(int u,int v,int w) { edge[tot].u = u; edge[tot].v = v; edge[tot].w = w; edge[tot].next = head[u]; head[u] = tot++; } int N; int dp[MAXN][2]; int dfs(int u,int sta,int fa) { if (dp[u][sta] < INF) return dp[u][sta]; dp[u][sta] = sta; for (int i = head[u] ; i != -1 ; i = edge[i].next) { int v = edge[i].v; if (v == fa) continue; if (sta == 0) dp[u][sta] += dfs(v,1,u); else dp[u][sta] += min(dfs(v,1,u),dfs(v,0,u)); } return dp[u][sta]; } int main() { while (scanf("%d",&N) != EOF) { init(); int root = 1; for (int i = 1 ; i <= N ; i++) { int u,cnt; scanf("%d:(%d)",&u,&cnt); if (cnt > 1) root = u; while (cnt--) { int x; scanf("%d",&x); add_edge(u,x,9797); add_edge(x,u,9797); // printf("%d %d\n",u,x); } } memset(dp,0x3f,sizeof(dp)); int ans = min(dfs(root,0,-1),dfs(root,1,-1)); printf("%d\n",ans); } return 0; }
HDU 1151 Air Raid
一个=城镇中有n个路口和m条单项的路径,图是无环图,现在要派一些伞兵去这些成寿寺,要到达所有的路口;
有一些或者没有伞兵可以不去那些路口,只要其他人能完成这么任务;
每个在一个路口着陆了的伞兵可以沿着街去到其他路口;
我们的任务就是求出去执行任务的伞兵最少可以使几个;
二分图最小路径覆盖
#include <map> #include <set> #include <list> #include <cmath> #include <ctime> #include <deque> #include <stack> #include <queue> #include <cctype> #include <cstdio> #include <string> #include <vector> #include <climits> #include <cstdlib> #include <cstring> #include <iostream> #include <algorithm> #define LL long long #define PI 3.1415926535897932626 using namespace std; int gcd(int a, int b) {return a % b == 0 ? b : gcd(b, a % b);} const int MAXN = 250; const int MAXM = MAXN * MAXN * 2; const int INF = 0x3f3f3f3f; struct Edge { int u,v,next; int w; }edge[MAXM]; int head[MAXN],tot; int N,M; void init() { tot = 0; memset(head,-1,sizeof(head)); } void add_edge(int u,int v,int w) { edge[tot].u = u; edge[tot].v = v; edge[tot].w = w; edge[tot].next = head[u]; head[u] = tot++; } bool used[MAXN]; int linker[MAXN]; bool dfs(int u) { for (int i = head[u] ; i != -1 ; i = edge[i].next) { int v = edge[i].v; if (!used[v]) { used[v] = true; if (linker[v] == -1 || dfs(linker[v])) { linker[v] = u; return true; } } } return false; } int calcu() { int ret = 0; memset(linker,-1,sizeof(linker)); for (int i = 1 ; i <= N ; i++) { memset(used,false,sizeof(used)); if (dfs(i)) ret++; } return ret; } int main() { int T; scanf("%d",&T); while (T--) { scanf("%d%d",&N,&M); init(); while (M--) { int u,v; scanf("%d%d",&u,&v); add_edge(u,v,9797); } printf("%d\n",N - calcu()); } return 0; }
POJ 2594 Treasure Exploration
每个点可以走多次的最小路径覆盖
用floyd预处理,然后一样做就行
#include <map> #include <set> #include <list> #include <cmath> #include <ctime> #include <deque> #include <stack> #include <queue> #include <cctype> #include <cstdio> #include <string> #include <vector> #include <climits> #include <cstdlib> #include <cstring> #include <iostream> #include <algorithm> #define LL long long #define PI 3.1415926535897932626 using namespace std; int gcd(int a, int b) {return a % b == 0 ? b : gcd(b, a % b);} const int MAXN = 510; const int MAXM = MAXN * MAXN * 2; const int INF = 0x3f3f3f3f; int N,M; struct Edge { int u,v,next; int w; }edge[MAXM]; int head[MAXN],tot; void init() { tot = 0; memset(head,-1,sizeof(head)); } void add_edge(int u,int v,int w) { edge[tot].u = u; edge[tot].v = v; edge[tot].w = w; edge[tot].next = head[u]; head[u] = tot++; } bool used[MAXN]; int linker[MAXN]; bool dfs(int u) { for (int i = head[u] ; i != -1 ; i = edge[i].next) { int v = edge[i].v; if (!used[v]) { used[v] = true; if (linker[v] == -1 || dfs(linker[v])) { linker[v] = u; return true; } } } return false; } int calcu() { int ret = 0; memset(linker,-1,sizeof(linker)); for (int i = 1 ; i <= N ; i++) { memset(used,false,sizeof(used)); if (dfs(i)) ret++; } return ret; } int G[MAXN][MAXN]; int main() { //freopen("sample.txt","r",stdin); while (scanf("%d%d",&N,&M) != EOF) { if (N == 0 && M == 0) break; memset(G,0x3f,sizeof(G)); for (int i = 1; i <= M ; i++) { int u,v; scanf("%d%d",&u,&v); G[u][v] = 1; } for (int k = 1; k <= N ; k++) { for (int i = 1 ; i <= N ; i++) { for (int j = 1 ; j <= N ; j++) G[i][j] = min(G[i][j],G[i][k] + G[k][j]); } } init(); for (int i = 1 ; i <= N ; i++) { for (int j = 1 ; j <= N ; j++) { if (G[i][j] < INF) add_edge(i,j,9797); } } printf("%d\n",N - calcu()); } return 0; }
HDU 3829 Cat VS Dog
建图方法:不管猫狗怎么样,如果2个小孩是矛盾的那么连一条边;然后求最大独立集
#include <map> #include <set> #include <list> #include <cmath> #include <ctime> #include <deque> #include <stack> #include <queue> #include <cctype> #include <cstdio> #include <string> #include <vector> #include <climits> #include <cstdlib> #include <cstring> #include <iostream> #include <algorithm> #define LL long long #define PI 3.1415926535897932626 using namespace std; int gcd(int a, int b) {return a % b == 0 ? b : gcd(b, a % b);} const int MAXN = 510; const int MAXM = MAXN * MAXN * 2; const int INF = 0x3f3f3f3f; int N,M; struct Edge { int u,v,next; int w; }edge[MAXM]; int head[MAXN],tot; void init() { tot = 0; memset(head,-1,sizeof(head)); } void add_edge(int u,int v,int w) { edge[tot].u = u; edge[tot].v = v; edge[tot].w = w; edge[tot].next = head[u]; head[u] = tot++; } bool used[MAXN]; int linker[MAXN]; bool dfs(int u) { for (int i = head[u] ; i != -1 ; i = edge[i].next) { int v = edge[i].v; if (!used[v]) { used[v] = true; if (linker[v] == -1 || dfs(linker[v])) { linker[v] = u; return true; } } } return false; } int calcu() { int ret = 0; memset(linker,-1,sizeof(linker)); for (int i = 0 ; i <= N ; i++) { memset(used,false,sizeof(used)); if (dfs(i)) ret++; } return ret; } int cat,dog; string like[MAXN],dislike[MAXN]; int main() { while (scanf("%d%d%d",&cat,&dog,&N) != EOF) { init(); for (int i = 0 ; i < N ; i++) { cin >> like[i] >> dislike[i]; } for (int i = 0 ; i < N ; i++) { for (int j = 0 ; j < N ; j++) { if (i == j) continue; if (like[i] == dislike[j] || dislike[i] == like[j]) add_edge(i,j,9797); } } printf("%d\n",N - calcu() / 2); } return 0; }
————————————————————————————————————————————————————————————————————————————————————————————
二分图多重匹配
Y部中的一个点可以连接X部的多个点。但是有连点的相关限制,求最大匹配数目的问题
二分图的多重匹配算法的实现类似于匈牙利算法,对于集合C中的元素xi,找到一个与其相连的元素yi后,检查匈牙利算法的两个条件是否成立,若yi未被匹配,则将
xi,yi匹配。否则,如果与yi匹配的元素已经达到上限,那么在所有与yi匹配的元素中选择一个元素,检查是否能找到一条增广路径,如果能,则让出位置,让xi与yi匹配。
模板
const int MAXN = 310; const int INF = 0x3f3f3f3f; bool G[MAXN][MAXN]; int dis[MAXN][MAXN]; bool used[MAXN]; int linker[MAXN][MAXN]; int num[MAXN];//Y部每个可以连接的个数,即容量 int NX,NY; int K,C,M; bool dfs(int u) { for (int i = 1 ; i <= NY ; i++) { if(G[u][i] && !used[i]) { used[i] = true; if (linker[i][0] < num[i]) { linker[i][++linker[i][0]] = u; return true; } else { for (int j = 1 ; j <= num[i] ; j++) { if (dfs(linker[i][j])) { linker[i][j] = u; return true; } } } } } return false; } int calcu(int mid) { for (int i = 0 ; i <= NY ; i++) linker[i][0] = 0; int ret = 0; for (int i = 1 ; i <= NX ; i++) { memset(used,false,sizeof(used)); if (dfs(i)) ret++; } return ret; }
POJ 2289 Jamie‘s Contact Groups
给出一个图,然后让你分组,每组的个数尽量少
二分
#include <map> #include <set> #include <list> #include <cmath> #include <ctime> #include <deque> #include <stack> #include <queue> #include <cctype> #include <cstdio> #include <string> #include <vector> #include <climits> #include <cstdlib> #include <cstring> #include <iostream> #include <algorithm> #define LL long long #define PI 3.1415926535897932626 using namespace std; int gcd(int a, int b) {return a % b == 0 ? b : gcd(b, a % b);} const int MAXN = 2010; bool G[MAXN][MAXN]; bool used[MAXN]; int NX,NY; char str[MAXN]; int cnt[MAXN]; int linker[MAXN][MAXN]; bool dfs(int u,int mid) { for (int i = 0 ; i < NY ; i++) { if (G[u][i] && !used[i]) { used[i] = true; if (linker[i][0] < mid) { linker[i][++linker[i][0]] = u; return true; } for (int j = 1 ; j <= linker[i][0] ; j++) { if (dfs(linker[i][j],mid)) { linker[i][j] = u; return true; } } } } return false; } bool calcu(int mid) { int ret = 0; for (int i = 0 ; i < NY ; i++) linker[i][0] = 0; for (int i = 0 ; i < NX ; i++) { memset(used,false,sizeof(used)); if (!dfs(i,mid)) return false; } return true; } int main() { int N,M; while (scanf("%d%d",&N,&M) != EOF) { if(N == 0 && M == 0) break; NX = N; NY = M; memset(G,false,sizeof(G)); for (int i = 0 ; i < NX ; i++) { char ch; int x; scanf("%s",str); while(scanf("%d%c",&x,&ch) != EOF) { G[i][x] = true; if(ch == ‘\n‘) break; } } //for (int i = 1 ; i <= 3 ; i++) printf("%d",calcu(i)); int L = 0,R = NX; int ans; while (L <= R) { int mid = (L + R) / 2; if (calcu(mid)) { R = mid - 1; ans = mid; } else L = mid + 1; } printf("%d\n",ans); } return 0; }
在一中网络流最大流写法
#include <map> #include <set> #include <list> #include <cmath> #include <ctime> #include <deque> #include <stack> #include <queue> #include <cctype> #include <cstdio> #include <string> #include <vector> #include <climits> #include <cstdlib> #include <cstring> #include <iostream> #include <algorithm> #define LL long long #define PI 3.1415926535897932626 using namespace std; int gcd(int a, int b) {return a % b == 0 ? b : gcd(b, a % b);} const int MAXN = 4010; const int MAXM = MAXN * 500; const int INF = 0x3f3f3f3f; vector<pair<int,int> >res; struct Edge { int u,v,next; int cap,flow; }edge[MAXM]; int head[MAXN],tot; int gap[MAXN],dep[MAXN],cur[MAXN]; int N,M; char str[MAXN]; void init() { memset(head,-1,sizeof(head)); tot = 0; } void add_edge(int u,int v,int cap,int rcap = 0) { edge[tot].u = u; edge[tot].v = v; edge[tot].cap = cap; edge[tot].flow = 0; edge[tot].next = head[u]; head[u] = tot++; edge[tot].u = v; edge[tot].v = u; edge[tot].cap = rcap; edge[tot].flow = 0; edge[tot].next = head[v]; head[v] = tot++; } int Q[MAXN]; void BFS(int st,int ed) { memset(dep,-1,sizeof(dep)); memset(gap,0,sizeof(gap)); gap[0] = 1; int front = 0 ,rear = 0; dep[ed] = 0; Q[rear++] = ed; while (front < rear) { int u = Q[front++]; for (int i = head[u] ; i != -1 ; i = edge[i].next) { int v = edge[i].v; if (dep[v] != -1) continue; Q[rear++] = v; dep[v] = dep[u] + 1; gap[dep[v]]++; } } } int S[MAXN]; int sap(int st,int ed,int N) { BFS(st,ed); memcpy(cur,head,sizeof(head)); int top = 0; int u = st; int ans = 0; while (dep[st] < N) { if (u == ed) { int Min = INF; int inser; for (int i = 0 ; i < top ; i++) { if (Min > edge[S[i]].cap - edge[S[i]].flow) { Min = edge[S[i]].cap - edge[S[i]].flow; inser = i; } } for (int i = 0 ; i < top ; i++) { edge[S[i]].flow += Min; edge[S[i] ^ 1].flow -=Min; } ans += Min; top = inser; u = edge[S[top] ^ 1].v; continue; } bool flag = false; int v; for (int i = cur[u] ; i != -1 ; i = edge[i].next) { v = edge[i].v; if (edge[i].cap - edge[i].flow && dep[v] + 1 == dep[u]) { flag = true; cur[u] = i; break; } } if(flag) { S[top++] = cur[u]; u = v; continue; } int Min = N; for (int i = head[u] ; i != -1 ; i = edge[i].next) { if (edge[i].cap - edge[i].flow && dep[edge[i].v] < Min) { Min = dep[edge[i].v]; cur[u] = i; } } gap[dep[u]]--; if (!gap[dep[u]]) return ans; dep[u] = Min + 1; gap[dep[u]]++; if (u != st) u = edge[S[--top] ^ 1].v; } return ans; } bool calcu(int mid) { init(); int source = N + M,target = N + M + 1; for (int i = 0 ; i < M ; i++) add_edge(source,i,mid); for (int i = 0 ; i < N ; i++) add_edge(M + i,target,1); for (int i = 0 ; i < (int)res.size() ; i++) { add_edge(res[i].second,res[i].first + M,1); } int ret = sap(source,target,N + M + 2); if(ret < N) return false; return true; } int main() { while (scanf("%d%d",&N,&M) != EOF) { if(N == 0 && M == 0) break; res.clear(); for (int i = 0 ; i < N ; i++) { char ch; int x; scanf("%s",str); while(scanf("%d%c",&x,&ch) != EOF) { res.push_back(make_pair(i,x)); if(ch == ‘\n‘) break; } } // for (int i = 1 ; i <= 3 ; i++) printf("%d",calcu(i)); // puts(""); int L = 0,R = N; int ans; while (L <= R) { int mid = (L + R) / 2; if (calcu(mid)) { R = mid - 1; ans = mid; } else L = mid + 1; } printf("%d\n",ans); } return 0; }
POJ 2112 Optimal Milking
#include <map> #include <set> #include <list> #include <cmath> #include <ctime> #include <deque> #include <stack> #include <queue> #include <cctype> #include <cstdio> #include <string> #include <vector> #include <climits> #include <cstdlib> #include <cstring> #include <iostream> #include <algorithm> #define LL long long #define PI 3.1415926535897932626 using namespace std; int gcd(int a, int b) {return a % b == 0 ? b : gcd(b, a % b);} const int MAXN = 310; const int INF = 0x3f3f3f3f; bool G[MAXN][MAXN]; int dis[MAXN][MAXN]; bool used[MAXN]; int linker[MAXN][MAXN]; int num[MAXN]; int NX,NY; int K,C,M; bool dfs(int u) { for (int i = 1 ; i <= NY ; i++) { if(G[u][i] && !used[i]) { used[i] = true; if (linker[i][0] < M) { linker[i][++linker[i][0]] = u; return true; } else { for (int j = 1 ; j <= M ; j++) { if (dfs(linker[i][j])) { linker[i][j] = u; return true; } } } } } return false; } bool calcu(int mid) { memset(G,false,sizeof(G)); for (int i = K + 1 ; i <= C + K ; i++) { int l = i - K; for (int j = 1 ; j <= K; j++) { int r = j; if (dis[i][j] <= mid) G[l][r] = true; } } for (int i = 0 ; i <= NY ; i++) linker[i][0] = 0; int ret = 0; for (int i = 1 ; i <= NX ; i++) { memset(used,false,sizeof(used)); if (dfs(i)) ret++; } if (ret < C) return false; return true; } int main() { while (scanf("%d%d%d",&K,&C,&M) != EOF) { for (int i = 1 ; i <= K + C ; i++) for (int j = 1 ; j <= K + C ; j++) { scanf("%d",&dis[i][j]); if(i != j && dis[i][j] == 0) dis[i][j] = INF; } for (int k = 1 ; k <= K + C ; k++) { for (int i = 1 ; i <= K + C ; i++) for (int j = 1 ; j <= K + C ; j++) dis[i][j] = min(dis[i][j],dis[i][k] + dis[k][j]); } NX = C; NY = K; int L = 0 ,R = INF; int ans; // for (int i = 1 ; i <= 3 ; i++)printf("%d",calcu(i));puts(""); while (L <= R) { int mid = (L + R) / 2; if (calcu(mid)) { ans = mid; R = mid - 1; } else L = mid + 1; } printf("%d\n",ans); } return 0; }
POJ 3189 Steady Cow Assignment
#include <map> #include <set> #include <list> #include <cmath> #include <ctime> #include <deque> #include <stack> #include <queue> #include <cctype> #include <cstdio> #include <string> #include <vector> #include <climits> #include <cstdlib> #include <cstring> #include <iostream> #include <algorithm> #define LL long long #define PI 3.1415926535897932626 using namespace std; int gcd(int a, int b) {return a % b == 0 ? b : gcd(b, a % b);} const int MAXN = 3010; const int INF = 0x3f3f3f3f; int dis[MAXN][MAXN]; bool used[MAXN]; int linker[MAXN][MAXN]; int num[MAXN]; int NX,NY; int N,B,L,R; int love[MAXN][MAXN]; bool dfs(int u) { for (int i = 1 ; i <= NY ; i++) { if(love[u][i] >= L && love[u][i] <= R && !used[i]) { used[i] = true; if (linker[i][0] < num[i]) { linker[i][++linker[i][0]] = u; return true; } else { for (int j = 1 ; j <= num[i] ; j++) { if (dfs(linker[i][j])) { linker[i][j] = u; return true; } } } } } return false; } bool calcu() { for (int i = 0 ; i <= NY ; i++) linker[i][0] = 0; for (int i = 1 ; i <= NX ; i++) { memset(used,false,sizeof(used)); if (!dfs(i)) return false; } return true; } int main() { while (scanf("%d%d",&N,&B) != EOF) { for (int i = 1 ; i <= N ; i++) { for (int j = 1 ; j <= B ; j++) { int x; scanf("%d",&x); love[i][x] = j; } } for (int i = 1 ; i <= B ; i++) scanf("%d",&num[i]); NX = N; NY = B; L = 1; R = 1; int ans = INF; while (L <= R && R <= B) { if (calcu()) { ans = min(ans,R - L + 1); L++; } else R++; } printf("%d\n",ans); } return 0; }
————————————————————————————————————————————————————————————————————————————————————————————
最大权KM匹配 这2道题都比较简单
模板:点从0开始编号
#include <map> #include <set> #include <list> #include <cmath> #include <ctime> #include <deque> #include <stack> #include <queue> #include <cctype> #include <cstdio> #include <string> #include <vector> #include <climits> #include <cstdlib> #include <cstring> #include <iostream> #include <algorithm> #define LL long long #define PI 3.1415926535897932626 using namespace std; int gcd(int a, int b) {return a % b == 0 ? b : gcd(b, a % b);} const int MAXN = 3010; const int INF = 0x3f3f3f3f; typedef int type; int nx,ny; type g[MAXN][MAXN]; int linker[MAXN]; type lx[MAXN],ly[MAXN]; type slack[MAXN]; bool visx[MAXN],visy[MAXN]; bool dfs(int x) { visx[x] = true; for (int y = 0 ; y < ny ; y++) { if (visy[y]) continue; type tmp = lx[x] + ly[y] - g[x][y]; if (tmp == 0) { visy[y] = true; if (linker[y] == -1 || dfs(linker[y])) { linker[y] = x; return true; } } else if (slack[y] > tmp) slack[y] = tmp; } return false; } type KM() { memset(linker,-1,sizeof(linker)); for (int i = 0 ; i < MAXN ; i++) ly[i] = 0.0; for (int i = 0 ; i < nx ; i++) { lx[i] = -INF; for (int j = 0 ; j < ny ; j++) if (g[i][j] - lx[i] > 0) lx[i] = g[i][j]; } for (int x = 0 ; x < nx ; x++) { for (int i = 0 ; i < ny ; i++) slack[i] = INF; while (true) { memset(visx,false,sizeof(visx)); memset(visy,false,sizeof(visy)); if (dfs(x)) break; type d = INF; for (int i = 0 ; i < ny ; i++) { if (!visy[i] && d - slack[i] > 0) d = slack[i]; } for (int i = 0 ; i < nx ; i++) { if (visx[i]) lx[i] -= d; } for (int i = 0 ; i < ny ; i++) { if (visy[i]) ly[i] += d; else slack[i] -= d; } } } type ret = 0; for (int i = 0 ; i < ny ; i++) if (linker[i] != -1) ret += g[linker[i]][i]; return ret; } int main() { int N; while (scanf("%d",&N) != EOF) { nx = N; ny = N; for (int i = 0 ; i < N ; i++) for (int j = 0 ; j < N ; j++) scanf("%d",&g[i][j]); printf("%d\n",KM()); } return 0; }
HDU 3488 Tour 最小权匹配+建图
每个点都在一个有向环内,那么对于每个环内的点,他的出度等于入度等于1
那么就可以拆点,一个入点,一个出点,然后就直接求最小权匹配
#include <map> #include <set> #include <list> #include <cmath> #include <ctime> #include <deque> #include <stack> #include <queue> #include <cctype> #include <cstdio> #include <string> #include <vector> #include <climits> #include <cstdlib> #include <cstring> #include <iostream> #include <algorithm> #define LL long long #define PI 3.1415926535897932626 using namespace std; int gcd(int a, int b) {return a % b == 0 ? b : gcd(b, a % b);} const int MAXN = 3010; const int INF = 0x3f3f3f3f; typedef int type; int nx,ny; type g[MAXN][MAXN]; int linker[MAXN]; type lx[MAXN],ly[MAXN]; type slack[MAXN]; bool visx[MAXN],visy[MAXN]; bool dfs(int x) { visx[x] = true; for (int y = 0 ; y < ny ; y++) { if (visy[y]) continue; type tmp = lx[x] + ly[y] - g[x][y]; if (tmp == 0) { visy[y] = true; if (linker[y] == -1 || dfs(linker[y])) { linker[y] = x; return true; } } else if (slack[y] > tmp) slack[y] = tmp; } return false; } type KM() { memset(linker,-1,sizeof(linker)); for (int i = 0 ; i < MAXN ; i++) ly[i] = 0.0; for (int i = 0 ; i < nx ; i++) { lx[i] = -INF; for (int j = 0 ; j < ny ; j++) if (g[i][j] - lx[i] > 0) lx[i] = g[i][j]; } for (int x = 0 ; x < nx ; x++) { for (int i = 0 ; i < ny ; i++) slack[i] = INF; while (true) { memset(visx,false,sizeof(visx)); memset(visy,false,sizeof(visy)); if (dfs(x)) break; type d = INF; for (int i = 0 ; i < ny ; i++) { if (!visy[i] && d - slack[i] > 0) d = slack[i]; } for (int i = 0 ; i < nx ; i++) { if (visx[i]) lx[i] -= d; } for (int i = 0 ; i < ny ; i++) { if (visy[i]) ly[i] += d; else slack[i] -= d; } } } type ret = 0; for (int i = 0 ; i < ny ; i++) if (linker[i] != -1) ret += g[linker[i]][i]; return -ret; } int main() { int T; scanf("%d",&T); while (T--) { int N,M; scanf("%d%d",&N,&M); for (int i = 0 ; i <= N ; i++) for (int j = 0 ; j <= N ; j++) g[i][j] = -INF; nx = ny = N; for (int i = 0 ; i < M ; i++) { int u,v,w; scanf("%d%d%d",&u,&v,&w); u--; v--; g[u][v] = max(g[u][v],-w); } printf("%d\n",KM()); } return 0; }
————————————————————————————————————————————————————————————————————————————————————————————
一般图匹配带花树
复制粘贴一下。我也不太懂。弱B
来自
http://fanhq666.blog.163.com/blog/static/8194342620120304463580/
在北京冬令营的时候,yby提到了“带花树开花”算法来解非二分图的最大匹配。
于是,我打算看看这是个什么玩意。其实之前,我已经对这个算法了解了个大概,但是。。。真的不敢去写。
有一个叫Galil Zvi的人(应该叫计算机科学家),写了篇论文:
Efficient Algorithms for Finding Maximal Matching in Graphs
(如果你在网上搜不到,可以:http://builtinclz.abcz8.com/art/2012/Galil%20Zvi.pdf)
这篇论文真神啊,它解决了4个问题:
(一般图+二分图)的(最大匹配+最大权匹配)问题。
算法的思想、故事,请自己看论文吧。
这个论文告诉了我们很多有趣的东西,例如:
用Dinic实现的二分图匹配的时间复杂度其实是O(M*N^0.5),这也许能够解释为什么一般网络流算法比Hungry要快了。
另外,带花树算法的正确性的证明比较困难;而其时间复杂度是可以做到O(M*N^0.5)的,不过要详细实现,那么就快能到“ACM最长论文奖”了。
我写了一个实例代码:
http://builtinclz.abcz8.com/art/2012/ural1099.cpp
没错,这是用来解决URAL 1099 Work Schedule那题的。时间复杂度是O(N^3)
简述一下“带花树”算法吧:
它的核心思想还是找增广路。假设已经匹配好了一堆点,我们从一个没有匹配的节点s开始,使用BFS生成搜索树。每当发现一个节点u,如果u还没有被匹配,那么就可以进行一次成功的增广;否则,我们就把节点u和它的配偶v一同接到树上,之后把v丢进队列继续搜索。我们给每个在搜索树上的点一个类型:S或者T。当u把它的配偶v扔进队列的时候,我们把u标记为T型,v标记为S型。于是,搜索树的样子是这样的:
s
/ \
a b
| |
c d
/ \ / \
e f u j
| | | |
i j v k
其中,黑色竖线相连的两个点是已经匹配好的,蓝色斜线表示两个点之间有边,但是没有配对。T型的用红色,S型的用黑色。
这里有个小问题:一个S型点d在某一步扩展的时候发现了点u,如果u已经在搜索树上了(即,出现了环),怎么办?
我们规定,如果u的类型是T型,就无视这次发现;(这意味着我们找到了一个长度为偶数的环,直接无视)
s
/ \
a b
| |
c d 如果连出来的边是指向T型点的,就无视这个边。
/ \ / \
e f<- g
| | |
i j k
否则,我们找到了一个长度为奇数的环,就要进行一次“缩花”的操作!所谓缩花操作,就是把这个环缩成一个点。
s
/ \
a b
| |
c d
/ \ / \
e f | g
| | | |
i u<-+ k
这个图缩花之后变成了5个点(一个大点,或者叫一朵花,加原来的4个点):
缩点完成之后,还要把原来环里面的T型点统统变成S型点,之后扔到队列里去。
+-------------+
| |
| s |
| / \ |
| a b |
| | | | 现在是一个点了!还是一个S点。
| c d |
| / \ / \ |
+-|-- f--u ---|---+
| | | |
| | | |
| | | |
| +-------------+ |
| |
e g
| |
i k
为什么能缩成一个点呢?我们看一个长度为奇数的环(例如上图中的s-b-d-u-f-c-a-),如果我们能够给它中的任意一个点找一个出度(配偶),那么环中的其他点正好可以配成对,这说明,每个点的出度都是等效的。例如,假设我们能够给图中的点d另找一个配偶(例如d'好了),那么,环中的剩下6个点正好能配成3对,一个不多,一个不少(算上d和d'一共4对刚刚好)。
a-s-b-d-d' a s-b d-d'
\ | => \
c-f-u c f-u
这就是我们缩点的思想来源。有一个劳苦功高的计算机科学家证明了:缩点之前和缩点之后的图是否有增广路的情况是相同的。
缩起来的点又叫一朵花(blossom).
注意到,组成一朵花的里面可能嵌套着更小的花。
当我们最终找到一条增广路的时候,要把嵌套着的花层层展开,还原出原图上的增广路出来。
嗯,现在你对实现这个算法有任何想法吗?
天啊,还要缩点……写死谁。。。。。。
我一开始也是这么想的。
我看了一眼网上某个大牛的程序,之后结合自己的想法,很努力地写出了一个能AC的版本。
实现的要点有什么呢?
首先,我们不“显式”地表示花。我们记录一个Next数组,表示最终增广的时候的路径上的后继。同时,我们维护一个并查集,表示每个点现在在以哪个点为根的花里(一个花被缩进另一朵花之后就不算花了)。还要记录每个点的标记。
主程序是一段BFS。对于每个由x发展出来的点y,分4种情况讨论:
1。xy是配偶(不说夫妻,这是非二分图。。。)或者xy现在是一个点(在一朵花里):直接无视
2。y是T型点:直接无视
3。y目前单身:太好了,进行增广!
4。y是一个S型点:缩点!缩点!
缩点的时候要进行的工作:
1。找x和y的LCA(的根)p。找LCA可以用各种方法。。。直接朴素也行。
2。在Next数组中把x和y接起来(表示它们形成环了!)
3。从x、y分别走到p,修改并查集使得它们都变成一家人,同时沿路把Next数组接起来。
Next数组很奇妙。每时每刻,它实际形成了若干个挂在一起的双向链表来表示一朵花内部的走法。
----
/ \<--+
| | |
| |<--+
v v
----------
/ \
+- --+
| |
| |
+---->s <------+
有权图的最大匹配怎么做?
看论文吧。。。用类似KM的方法,不过,是给每个花再来一个权值。真的很复杂。。。
有一个人写了代码,好像是GPL许可证。。。你最好想办法搜到它的网站来看看版权的问题;总之,我先贴出来:
http://builtinclz.abcz8.com/art/2012/mwmatching.py
URAL 1099 模板题
给出一个图,问你最多匹配几个点,输出匹配点
#include <map> #include <set> #include <list> #include <cmath> #include <ctime> #include <deque> #include <stack> #include <queue> #include <cctype> #include <cstdio> #include <string> #include <vector> #include <climits> #include <cstdlib> #include <cstring> #include <iostream> #include <algorithm> #define LL long long #define PI 3.1415926535897932626 using namespace std; int gcd(int a, int b) {return a % b == 0 ? b : gcd(b, a % b);} const int MAXN = 300; int N; //点的个数,点的编号从1到N bool Graph[MAXN][MAXN]; int Match[MAXN]; bool InQueue[MAXN],InPath[MAXN],InBlossom[MAXN]; int Head,Tail; int Queue[MAXN]; int Start,Finish; int NewBase; int Father[MAXN],Base[MAXN]; int Count;//匹配数,匹配对数是Count/2 void CreateGraph() { int u,v; memset(Graph,false,sizeof(Graph)); scanf("%d",&N); while(scanf("%d%d",&u,&v) == 2) { Graph[u][v] = Graph[v][u] = true; } } void Push(int u) { Queue[Tail] = u; Tail++; InQueue[u] = true; } int Pop() { int res = Queue[Head]; Head++; return res; } int FindCommonAncestor(int u,int v) { memset(InPath,false,sizeof(InPath)); while(true) { u = Base[u]; InPath[u] = true; if(u == Start) break; u = Father[Match[u]]; } while(true) { v = Base[v]; if(InPath[v])break; v = Father[Match[v]]; } return v; } void ResetTrace(int u) { int v; while(Base[u] != NewBase) { v = Match[u]; InBlossom[Base[u]] = InBlossom[Base[v]] = true; u = Father[v]; if(Base[u] != NewBase) Father[u] = v; } } void BloosomContract(int u,int v) { NewBase = FindCommonAncestor(u,v); memset(InBlossom,false,sizeof(InBlossom)); ResetTrace(u); ResetTrace(v); if(Base[u] != NewBase) Father[u] = v; if(Base[v] != NewBase) Father[v] = u; for(int tu = 1; tu <= N; tu++) if(InBlossom[Base[tu]]) { Base[tu] = NewBase; if(!InQueue[tu]) Push(tu); } } void FindAugmentingPath() { memset(InQueue,false,sizeof(InQueue)); memset(Father,0,sizeof(Father)); for(int i = 1; i <= N; i++) Base[i] = i; Head = Tail = 1; Push(Start); Finish = 0; while(Head < Tail) { int u = Pop(); for(int v = 1; v <= N; v++) if(Graph[u][v] && (Base[u] != Base[v]) && (Match[u] != v)) { if((v == Start) || ((Match[v] > 0) && Father[Match[v]] > 0)) BloosomContract(u,v); else if(Father[v] == 0) { Father[v] = u; if(Match[v] > 0) Push(Match[v]); else { Finish = v; return; } } } } } void AugmentPath() { int u,v,w; u = Finish; while(u > 0) { v = Father[u]; w = Match[v]; Match[v] = u; Match[u] = v; u = w; } } void Edmonds() { memset(Match,0,sizeof(Match)); for(int u = 1; u <= N; u++) if(Match[u] == 0) { Start = u; FindAugmentingPath(); if(Finish > 0)AugmentPath(); } } void PrintMatch() { Count = 0; for(int u = 1; u <= N; u++) if(Match[u] > 0) Count++; printf("%d\n",Count); for(int u = 1; u <= N; u++) if(u < Match[u]) printf("%d %d\n",u,Match[u]); } int main() { CreateGraph();//建图 Edmonds();//进行匹配 PrintMatch();//输出匹配数和匹配 return 0; }
HDU 4687 Boke and Tsukkomi
暴力删边,计算新图的最大匹配如果小于原图匹配-1.就不是最大匹配里的边。
#include <map> #include <set> #include <list> #include <cmath> #include <ctime> #include <deque> #include <stack> #include <queue> #include <cctype> #include <cstdio> #include <string> #include <vector> #include <climits> #include <cstdlib> #include <cstring> #include <iostream> #include <algorithm> #define LL long long #define PI 3.1415926535897932626 using namespace std; int gcd(int a, int b) {return a % b == 0 ? b : gcd(b, a % b);} const int MAXN = 70; int N; //点的个数,点的编号从1到N bool Graph[MAXN][MAXN]; int Match[MAXN]; bool InQueue[MAXN],InPath[MAXN],InBlossom[MAXN]; int Head,Tail; int Queue[MAXN]; int Start,Finish; int NewBase; int Father[MAXN],Base[MAXN]; int Count;//匹配数,匹配对数是Count/2 void CreateGraph() { int u,v; memset(Graph,false,sizeof(Graph)); scanf("%d",&N); while(scanf("%d%d",&u,&v) == 2) { Graph[u][v] = Graph[v][u] = true; } } void Push(int u) { Queue[Tail] = u; Tail++; InQueue[u] = true; } int Pop() { int res = Queue[Head]; Head++; return res; } int FindCommonAncestor(int u,int v) { memset(InPath,false,sizeof(InPath)); while(true) { u = Base[u]; InPath[u] = true; if(u == Start) break; u = Father[Match[u]]; } while(true) { v = Base[v]; if(InPath[v])break; v = Father[Match[v]]; } return v; } void ResetTrace(int u) { int v; while(Base[u] != NewBase) { v = Match[u]; InBlossom[Base[u]] = InBlossom[Base[v]] = true; u = Father[v]; if(Base[u] != NewBase) Father[u] = v; } } void BloosomContract(int u,int v) { NewBase = FindCommonAncestor(u,v); memset(InBlossom,false,sizeof(InBlossom)); ResetTrace(u); ResetTrace(v); if(Base[u] != NewBase) Father[u] = v; if(Base[v] != NewBase) Father[v] = u; for(int tu = 1; tu <= N; tu++) if(InBlossom[Base[tu]]) { Base[tu] = NewBase; if(!InQueue[tu]) Push(tu); } } void FindAugmentingPath() { memset(InQueue,false,sizeof(InQueue)); memset(Father,0,sizeof(Father)); for(int i = 1; i <= N; i++) Base[i] = i; Head = Tail = 1; Push(Start); Finish = 0; while(Head < Tail) { int u = Pop(); for(int v = 1; v <= N; v++) if(Graph[u][v] && (Base[u] != Base[v]) && (Match[u] != v)) { if((v == Start) || ((Match[v] > 0) && Father[Match[v]] > 0)) BloosomContract(u,v); else if(Father[v] == 0) { Father[v] = u; if(Match[v] > 0) Push(Match[v]); else { Finish = v; return; } } } } } void AugmentPath() { int u,v,w; u = Finish; while(u > 0) { v = Father[u]; w = Match[v]; Match[v] = u; Match[u] = v; u = w; } } void Edmonds() { memset(Match,0,sizeof(Match)); for(int u = 1; u <= N; u++) if(Match[u] == 0) { Start = u; FindAugmentingPath(); if(Finish > 0)AugmentPath(); } } int PrintMatch() { Edmonds(); Count = 0; for(int u = 1; u <= N; u++) if(Match[u] > 0) Count++; return Count / 2; } bool g[MAXN][MAXN]; vector<pair<int,int> >res; int main() { int M; while (scanf("%d%d",&N,&M) != EOF) { res.clear(); memset(g,false,sizeof(g)); memset(Graph,false,sizeof(Graph)); for (int i = 1 ; i <= M ; i++) { int u,v; scanf("%d%d",&u,&v); res.push_back(make_pair(u,v)); g[u][v] = g[v][u] = true; Graph[u][v] = Graph[v][u] = true; } int tot = PrintMatch(); vector<int>ans; for (int i = 0 ; i < M ; i++) { int u = res[i].first; int v = res[i].second; memcpy(Graph,g,sizeof(g)); for (int j = 1 ; j <= N ; j++) Graph[u][j] = Graph[j][u] = Graph[v][j] = Graph[j][v] = false; int ret = PrintMatch(); if (ret < tot - 1) ans.push_back(i + 1); } printf("%d\n",ans.size()); int sz = ans.size(); for (int i = 0 ; i < sz ; i++) { printf("%d",ans[i]); if (i < sz - 1) putchar(‘ ‘); } puts(""); } return 0; }