嗯。。。。今天开始写博客。。。。大一过去一半了。。。希望下半年能有个好的开始吧
先贴上百度百科里面对匈牙利算法的描述
算法轮廓:
⑴置M(这里M是图的一个子边集, 也就是现在要求的最大匹配的边集)为空
⑵找出一条 增广路径P,通过异或操作获得更大的 匹配M’代替M
⑶重复⑵操作直到找不出 增广路径为止
而在代码的实现中,我们考虑枚举二分图左边点集中的点x的所有出边指向的点y,若y之前没有被匹配,那么(x,y)就是一对可以的匹配,我们便
将匹配数+1,。否则,我们便考虑给算法中已经于y匹配的点x'另找一个匹配,如果这时我们给x'找到了另外的匹配,那么(x,y)便可以成为一对行
的匹配。给x'寻找匹配的过程我们可以用dfs解决。从而我们有了解决最大匹配的方法。
下面给出我的模板
vector<int> v[]; //储存边的邻接表 int pre[]; // pre[i]记录与i匹配的左边点集中的点 bool flag[]; //记录是否访问过某一个点 bool find(int x) { int len = v[x].size(); rep(i, 0, len) { if(!flag[v[x][i]]) { flag[v[x][i]] = true; if(pre[v[x][i]] == -1 || find(pre[v[x][i]])) { pre[v[x][i]] = x; return true; } } } return false; } int hungary(int n) { int ans = 0; memset(pre, 255, sizeof(pre)); rep(i, 0, n) { memset(flag, 0, sizeof(flag)); if(find(i)) ans++; } return ans;//ans即为所求的最大匹配 }
然后以hdu的1054作为例题
题目传送门http://acm.hdu.edu.cn/showproblem.php?pid=1054
(另一道poj上的二分匹配,不过加了二分枚举答案一步,链接点这儿)
完全就是简单的匹配,不过建的是无向图而已
由于模板是在这道题做出之后才有的。。。所以模板的形式和题目AC的代码略有不同。。。
#include <iostream> #include <cstdio> #include <vector> using namespace std; #define MAX_N 1505 int pre[MAX_N]; bool flag[MAX_N]; vector<int> map[MAX_N]; int n; int find(int cur) { int k; vector<int>::iterator iter, end = map[cur].end(); for (iter = map[cur].begin(); iter < end; iter++) { k = *iter; if (!flag[k]) { flag[k] = true; if (pre[k] == -1 || find(pre[k])) { pre[k] = cur; return 1; } } } return 0; } int main() { int i, j, r, k, num, sum; while (scanf("%d", &n) != EOF) { memset(pre, -1, sizeof(pre)); for (i = 0; i < n; i++) map[i].clear(); for (i = 0; i < n; i++) { scanf("%d:(%d)", &k, &num); for (j = 0; j < num; j++) { scanf("%d", &r); map[k].push_back(r); map[r].push_back(k); } } sum = 0; for (i = 0; i < n; i++) { memset(flag, false, sizeof(flag)); sum += find(i); } printf("%d\n", sum / 2); } return 0; }
当然这个问题里面还涉及到了最小点覆盖和最大匹配的关系,明显图中所有的点都可以在最大匹配的边集中找到包含这个点的边,所以只要从每个边上
各选一个点的话,这时这些点肯定满足覆盖。而如果减少一个点的话,这些点就相当于从一个不完全匹配的边集中找到的点,这时肯定有边集没有涉及
到的点,所以可以保证这时的答案是最小的符合条件的。而至于答案中是sum/2,是因为无向图的话,这个算法对同一条边会求两次,所以会除以2