二分图相关概念 二分图最大匹配 二分图最大权匹配 poj3041 poj2195
来源:互联网 发布:流程图描述KMP算法 编辑:程序博客网 时间:2024/06/05 07:39
昨天在codefoces上见了一个二分图相关的题目(http://codeforces.com/problemset/problem/741/C),今天周末没事。就复习《算法竞赛入门经典》总结了一下二分图的相关概念,以及经典的二分图最大匹配算法,二分图最大权匹配算法。
先安利一波概念和性质:
二分图:假设图G = (V, E)是一个无向图,若顶点集可以分解成两个互不相交的子集(A, B),并且图中的所有边(i, j)的端点分别属于子集A,B中的元素,则称图G是一个二分图。常记为G(A,E,B)
匹配: 没有公共顶点的边的集合
最大匹配:最多的匹配数(选尽量多的边,使得任意两条选中的边中没有 公共的端点)
最大边独立集:最多的没有公共点的边数 (最大边独立 = 最大匹配)
最大独立点(独立集):最大的任意两点之间不存在边的子集 (最大点独立 = 顶点总数 - 最大匹配)
最小点集覆盖:覆盖所有边的最小的点集 (最小点集覆盖 = 最大匹配)
最小边覆盖:覆盖所有点的最小的边集 (最小边覆盖 = 顶点总数- 最大匹配)
最小路径覆盖:在图中找一些路径,使之覆盖了图中的所有顶点,且任何一个顶点有且只有一条路径与之关联。其中,路径数目最少的就是最小路径覆盖。(最小路径覆盖 = 顶点总数- 最大匹配)
最小路径覆盖针对有向图:《算法竞赛入门》是这样总结的:
DAG最小路径覆盖的解决办法:把所有的节点i拆为X节点i和Y接点i',如果图G中存在有向边i->j,则在二分图中引入边( i->j' )。设二分图的最大匹配数为m, 则结果就是n-m(n为顶点个数)。因为匹配和路径覆盖是一一对应的。对于路径覆盖中的每条简单路径,除了最后一个“结尾节点”之外都有唯一的后继和它对应(即匹配节点),因此,匹配数就是非结尾节点的个数。当匹配数最大时,非结尾节点的个数也将达到最大。此时结尾节点的个数最少,即路径最少。
由二分图的性质可以看出,求二分图的最大匹配是关键问题。许多问题都可以转化为求二分图的最大匹配。今天也学习了一下求二分图最大匹配的匈牙利算法:对于X集合中的每一个节点x,每次去寻找对应的Y集合中的匹配的顶点,如果正好找到就记录(下面代码用left[]数组存Y集合中对应X集合中的点),如果在寻找过程中发现x的匹配的点已经被前面的点占用,就回溯,尝试让前面的已经匹配的节点腾出空间。如果都不能,就放弃,即这个点就不在最大匹配中。
具体实现代码如下(poj3041):
#include <cstdio>#include <cstring>#include <algorithm>const int MAX = 501;int a[MAX][MAX], left[MAX], vis[MAX];bool match(int r, int n){for (int i = 1; i<=n; i++) if (a[r][i] && !vis[i]){vis[i] = true;if (!left[i] || match(left[i], n)){left[i] = r;return true;}}return false;}int main(int argc, char const *argv[]){int n, k;while (scanf("%d%d", &n, &k) != EOF){memset(a, 0, sizeof(a));memset(left, 0, sizeof(left));int x, y;for (int i = 0; i<k; i++){scanf("%d%d", &x, &y);a[x][y] = 1;}for (int i = 1; i<=n; i++){memset(vis, 0, sizeof(vis)); //vis[]标记,每次寻找时已经匹配过的Y集合中的节点,所以每次寻找时都需要重新标记match(i, n);}int res = 0;for (int i = 1; i<=n; i++) if (left[i]){//printf("%d %d\n", left[i], i);res++;}printf("%d\n", res);}return 0;}
具体实现代码如下(poj2195):
#include <cstdio>#include <cstring>#include <cmath>#include <algorithm>using namespace std;const int MAX = 101;const int INF = 1000000000;char s[MAX*1000];struct Node{int x, y;}p[MAX], q[MAX];int a[MAX][MAX], left[MAX], l[MAX], r[MAX];bool S[MAX], T[MAX];bool match(int root, int n){S[root] = true;for (int i = 1; i<=n; i++) if (l[root] + r[i] == a[root][i] && !T[i]){T[i] = true;if (!left[i] || match(left[i], n)){left[i] = root;return true;}}return false;}void update(int n){int aw = INF;for (int i = 1; i<=n; i++) if (S[i]){for (int j = 1; j<=n; j++) if (!T[j]) {aw = min(aw, l[i] + r[j] - a[i][j]);}}for (int i = 1; i<=n; i++){ if (S[i]) l[i] -= aw; if (T[i]) r[i] += aw;}}int main(int argc, char const *argv[]){/* code */int n, m;while (scanf("%d%d", &n,&m) && n != 0){int cnth = 0, cntm = 0;for (int i = 0; i<n; i++){scanf("%s", s);for (int j = 0; j<m; j++){if (s[j] == 'H'){p[++cnth] = (Node){i, j};}else if (s[j] == 'm'){q[++cntm] = (Node){i, j};}}}memset(a, 0, sizeof(a));for (int i = 1; i<=cntm; i++){for (int j = 1; j<=cnth; j++){a[i][j] = -(abs(p[i].x - q[j].x) + abs(p[i].y-q[j].y));}}/*for (int i = 1; i<=cntm; i++){for (int j = 1; j<=cnth; j++){printf("%d ", a[i][j]);}printf("\n");}*/n = cnth;for (int i = 1; i<=n; i++){l[i] = -INF;r[i] = 0;for (int j = 1; j<=n; j++){l[i] = max(l[i], a[i][j]);}}memset(left, 0, sizeof(left));for (int i = 1; i<=n; i++){for (;;){memset(S, false, sizeof(S));memset(T, false, sizeof(T));if (match(i, n)){break;}else{update(n);}}}int res = 0;for (int i = 1; i<=n; i++){res += -(l[i]+r[i]);}printf("%d\n", res);} return 0;}
上面的求最大权匹配的算法时间复杂度为O(n^4),其中每次更新a值的复杂度为O(n^2)。可以给Y中每个节点y定义一个松弛量 slack[y] = min{Lx(x) + Ly(y) - w(x, y)}。每次寻找匹配边的时候初始化slack,然后匹配的时候当遇到Lx(x) + Ly(y) != w(x, y)时,去更新slack。然后,在找最小的a时,只需要找slack中的最小值,时间复杂度为O(n),然后总时间复杂度降为O(n^3)。
具体时实现如下(poj 2195改进)
#include <cstdio>#include <cstring>#include <cmath>#include <algorithm>using namespace std;const int MAX = 101;const int INF = 1000000000;char s[MAX*1000];struct Node{int x, y;}p[MAX], q[MAX];int a[MAX][MAX], left[MAX], l[MAX], r[MAX], slack[MAX];bool S[MAX], T[MAX];bool match(int root, int n){S[root] = true;for (int i = 1; i<=n; i++) {if (T[i]){continue;}if (l[root] + r[i] != a[root][i]){slack[i] = min(slack[i], l[root]+r[i] - a[root][i]);continue;}if (l[root] + r[i] == a[root][i] && !T[i]){T[i] = true;if (!left[i] || match(left[i], n)){left[i] = root;return true;}}}return false;}void update(int n){int aw = INF;/*for (int i = 1; i<=n; i++) if (S[i]){for (int j = 1; j<=n; j++) if (!T[j]) {aw = min(aw, l[i] + r[j] - a[i][j]);}}*/for (int i = 1; i<=n; i++){aw = min(aw, slack[i]);}for (int i = 1; i<=n; i++){ if (S[i]) l[i] -= aw; if (T[i]) r[i] += aw;}}int main(int argc, char const *argv[]){/* code */int n, m;while (scanf("%d%d", &n,&m) && n != 0){int cnth = 0, cntm = 0;for (int i = 0; i<n; i++){scanf("%s", s);for (int j = 0; j<m; j++){if (s[j] == 'H'){p[++cnth] = (Node){i, j};}else if (s[j] == 'm'){q[++cntm] = (Node){i, j};}}}memset(a, 0, sizeof(a));for (int i = 1; i<=cntm; i++){for (int j = 1; j<=cnth; j++){a[i][j] = -(abs(p[i].x - q[j].x) + abs(p[i].y-q[j].y));}}n = cnth;for (int i = 1; i<=n; i++){l[i] = -INF;r[i] = 0;for (int j = 1; j<=n; j++){l[i] = max(l[i], a[i][j]);}}memset(left, 0, sizeof(left));for (int i = 1; i<=n; i++){for (;;){memset(S, false, sizeof(S));memset(T, false, sizeof(T));for (int j = 1; j<=n; j++) slack[j] = INF;if (match(i, n)){break;}else{update(n);}}}int res = 0;for (int i = 1; i<=n; i++){res += -(l[i]+r[i]);}printf("%d\n", res);} return 0;}
- 二分图相关概念 二分图最大匹配 二分图最大权匹配 poj3041 poj2195
- poj3041-二分图最大匹配
- POJ3041 二分图最大匹配
- 二分图匹配 POJ2195
- POJ3041--Asteroids--二分图最大匹配--Konig
- poj3041 二分图 最大匹配数
- (二分图最大匹配) poj3041 Asteroids
- poj3041 匈牙利算法 二分图最大匹配
- POJ3041--二分图最大匹配模板
- 二分图 最佳匹配 最大权匹配
- 二分图匹配应用。。poj3041
- poj3041--二分图匹配算法
- POJ3041(二分图匹配)
- 二分图最大权匹配算法模板
- 【二分图最大权匹配---KM算法】
- 二分图最大权匹配 (KM算法)
- 二分图的最大权匹配
- 二分图最大权匹配-km算法
- 机器学习(2)--感知机
- Android EditText清除焦点
- maven项目 错误: 找不到或无法加载主类
- jQuery中 :first选择器,first()和:first-child选择器的区别
- 2016.12.03【初中部 NOIP提高C组】模拟赛
- 二分图相关概念 二分图最大匹配 二分图最大权匹配 poj3041 poj2195
- 验证码识别技术的操作与调试
- 文件拷贝程序
- 2.Ext JS MVVM特性
- python @的作用
- Android中m、mm、mmm、mma、mmma的区别
- 面向对象(捕获异常try-catch-finally、throws抛异常)
- 四天学会mongoDB (第二天 细说增删改查)
- BLOB数据类型文件[PDF]的存取(Spring+Mybatis+Db2+Maven)