首先注意区分“最小路径覆盖”(minimum path cover)和“最小边覆盖”(minimum edge cover)以及“最小点覆盖”(minimum vertex cover)之间的区别。详细资料可以查询Wiki。
最小路径覆盖可以转化为二分图的最大匹配(maximum bipartite matching) 。公式为最小路径覆盖数=原图节点数-二分图最大匹配数。
求最大匹配的方法有两种:一是将二分图增添两个节点(source起点和sink终点)构造一个流网络(flow network),然后就可以按求最大流的算法来计算,基本的算法有Ford-Fulkerson算法。不过这样实现起来比较麻烦,下面着重介绍第二种方法:匈牙利算法(Hungarian algorithm )。
匈牙利算法其实和基于增广路径(augmenting path)的最大流算法是比较相似的。不同点在于:
一,匈牙利算法不需要构造流网络,也就是说不需要增添节点,并且二分图是无向图,不需要转换为流网络中的有向图。
二,匈牙利算法的增广路径(设为P)有附加条件:P的第一条边必须是尚未匹配的边,第二条边是已经匹配的边,如此未匹配和已匹配的边交替出现,最后一条边仍然是未匹配边(注意P可以是只有一条未匹配边的路径)。根据增广路径的附加条件可以看出一个特点:P的未匹配边一定比已匹配边多一条。
三,匈牙利算法依次对二分图的半边(左半边或者右半边,条件是节点数少的一边)的每个节点开始找增广路径,如果找到则取反:未匹配的边变成匹配边,已匹配边去掉匹配关系。这样的结果是路径P的匹配边数量增加一条,未匹配边数量减少一条。寻找增广路径的方法为DFS或者BFS递归。
四,当二分图中不存在增广路径时即产生最大匹配数。
匈牙利算法的实质就是找增广路径,并对增广路径做取反操作,直到没有增广路径为止。
匈牙利算法的基本流程:
初始时最大匹配为空
for
二分图左半边的每个点i
do
从点i出发寻找增广路径。如果找到,则把它取反(即增加了总了匹配数)。
下面是poj1422匈牙利算法的实现:
Code
1 //匈牙利算法实现
2 #include <iostream>
3 #include <string>
4 #define SIZE 121
5 using namespace std;
6
7 int vtx, eg;
8 int match[SIZE]; //存储与二分图右边集合的节点相匹配的左集合节点的索引
9 bool visited[SIZE]; //右边集合相应节点是否被访问过
10 bool map[SIZE][SIZE]; //图的邻接矩阵
11
12 //DFS搜索是否有增广路径
13 bool dfs(int left)
14 {
15 int t, i;
16 for (i=1;i<=vtx;i++){
17 if (map[left][i] && !visited[i]){
18 visited[i]=true;
19 t=match[i]; //将match[i]临时保存
20 match[i]=left; //路径取反操作
21 if (t==-1 || dfs(t)){//寻找是否为增广路径
22 return true;
23 }
24 match[i]=t; //如果不是增广路径,路径恢复原值
25 }
26 }
27 return false;
28 }
29
30 int main()
31 {
32 int test, u, v, i, sum;
33 freopen("input.txt", "r", stdin);
34 cin >> test;
35 while (test--){
36 cin >> vtx >> eg;
37 memset(map, 0, sizeof(map));
38 memset(match, -1, sizeof(match));
39 sum=0;
40 for (i=0;i<eg;i++){
41 cin >> u >> v;
42 map[u][v]=true;
43 }
44
45 for (i=1;i<=vtx;i++){
46 memset(visited, 0, sizeof(visited));
47 if (dfs(i)){
48 sum++;
49 }
50 }
51 cout << vtx-sum << endl;
52 }
53 return 0;
54 }
poj1469匈牙利算法:
Code
#include <iostream>
#define cNUM 101
#define sNUM 301
using namespace std;
int P, N;
int map[cNUM][sNUM];
int match[sNUM];
int vsted[sNUM];
bool Hungarian(int left)
{
int i, t;
for (i=1;i<=sNUM;i++){
if (map[left][i] && !vsted[i]){
vsted[i]=1;
t=match[i];
match[i]=left;
if (t==-1 || Hungarian(t)){
return true;
}
match[i]=t;
}
}
return false;
}
int main()
{
int test, cnt, i, j, st, sum;
//freopen("input.txt", "r", stdin);
scanf("%d", &test);
while (test--){
memset(match, -1, sizeof(match));
memset(map, 0, sizeof(map));
scanf("%d %d", &P, &N);
for (i=1;i<=P;i++){
scanf("%d", &cnt);
for (j=1;j<=cnt;j++){
scanf("%d", &st);
map[i][st]=1;
}
}
//Hungarian algorithm
sum=0;
for (i=1;i<=cNUM;i++){
memset(vsted, 0, sizeof(vsted));
if (Hungarian(i))
sum++;
}
if (sum==(P<N?P:N)){
printf("YES\n");
}
else{
printf("NO\n");
}
}
return 0;
}
参考:用匈牙利算法求二分图的最大匹配
http://www.matrix67.com/blog/archives/39
http://blog.163.com/baobao_zhang@126/blog/static/48252367200862682748461/
http://old.blog.edu.cn/user3/Hailer/archives/2007/1829623.shtml
PS:很神奇的一件事,Air Raid这题用EK最大流算法在zoj上WA,在poj上居然AC了。。。以下是poj1422EK算法的实现:
Code
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #define GSIZE 251
5
6 typedef struct{
7 int queue[GSIZE];
8 int head;
9 int tail;
10 }QUEUE;
11
12 int vtx, eg;
13 QUEUE q;
14 int color[GSIZE], par[GSIZE];
15 int resNet[GSIZE][GSIZE]; //模拟链表:y轴为节点索引,每一行存放此节点指向的节点索引,
16 //若为-1则说明指向的节点为空
17 int InitQueue(){
18 q.head=q.tail=0;
19 return 1;
20 }
21 int EnQueue(int x){
22 q.queue[q.tail]=x;
23 q.tail++;
24 return 1;
25 }
26 int DeQueue(){
27 int x;
28 x=q.queue[q.head];
29 q.head++;
30 return x;
31 }
32
33 int BFSFindPath()
34 {
35 int flag, i, u, v;
36 flag=0;
37 InitQueue();
38 for (i=1;i<=vtx;i++){
39 color[i]=color[i+120]=0;
40 par[i]=par[i+120]=-1;
41 }
42 color[250]=0;
43 par[250]=-1;
44 color[0]=1;
45 par[0]=-1;
46 EnQueue(0);
47 while (q.head!=q.tail && flag==0){
48 u=DeQueue();
49 i=0;
50 while (resNet[u][i]!=-1 && resNet[u][i]!=0 && i<GSIZE){
51 v=resNet[u][i];
52 if (color[v]==0){
53 color[v]=1;
54 par[v]=u;
55 EnQueue(v);
56 }
57 if (v==250){//当搜索到一条最短路径后停止
58 flag=1;
59 break;
60 }
61 i++;
62 }
63 color[u]=2;
64 }
65 return flag;
66 }
67 int Ford_Fulkerson()
68 {
69 int pathNum, v, i, del;
70 pathNum=0;
71 while (BFSFindPath()){
72 v=250;
73 //par[v]---->v修改为v--->par[v]
74 while (par[v]!=-1){
75 i=0;
76 while (resNet[v][i]!=-1 && i<GSIZE){
77 i++;
78 }
79 resNet[v][i]=par[v];
80 i=0;
81 while (resNet[par[v]][i]!=-1 && i<GSIZE){
82 if (resNet[par[v]][i]==v){
83 del=i;
84 }
85 i++;
86 }
87 resNet[par[v]][del]=resNet[par[v]][i-1];
88 resNet[par[v]][i-1]=-1;
89
90 v=par[v];
91 }
92 pathNum++;
93 }
94 return pathNum;
95 }
96 int main()
97 {
98 int c, i, st, end, cnt, ans;
99 //freopen("input.txt", "r", stdin);
100 scanf("%d", &c);
101 while (c--){
102 memset(resNet, -1, sizeof(resNet));
103 scanf("%d %d", &vtx, &eg);
104 //设起点下标0,终点下标250,构造剩余网络
105 for (i=1;i<=vtx;i++){
106 resNet[0][i-1]=i;
107 resNet[i+120][0]=250;
108 }
109 for (i=1;i<=eg;i++){
110 cnt=0;
111 scanf("%d %d", &st, &end);
112 while (resNet[st][cnt]!=-1){
113 cnt++;
114 }
115 resNet[st][cnt]=end+120;
116 }
117 if (eg==0){
118 ans=vtx;
119 }
120 else{
121 ans=vtx-Ford_Fulkerson();
122 }
123 printf("%d\n", ans);
124 }
125 return 0;
126 }