二分图构图的特点是:先根据题意确定考察点,然后再判断构造的图模型是否是二分图,或者能否转换为二分图,然后根据划分关系是否明确来定性边有无方向。最后,用二分图匹配算法解决之。
1: HDU 过山车
http://acm.hdu.edu.cn/showproblem.php?pid=2063
分析:直观的二分图题,以男女为二分图的两部分建图即可;
1 #include <cstdio> 2 #include <cstring> 3 using namespace std; 4 #define N 505 5 int k, n, m; 6 bool partner[N][N], used[N]; 7 int match[N]; 8 9 bool find(int x) 10 { 11 for (int i=1; i<=n; i++) 12 { 13 if (!used[i] && partner[x][i]) 14 { 15 used[i] = true; 16 if (match[i]==-1 || find(match[i])) 17 { 18 match[i] = x; 19 return true; 20 } 21 } 22 } 23 return false; 24 } 25 26 void Hungary () 27 { 28 int cnt=0; 29 memset (match, -1, sizeof match); 30 for (int i=1; i<=m; i++) 31 { 32 memset (used, 0, sizeof used); 33 if (find(i)) 34 cnt++; 35 } 36 printf ("%d\n",cnt); 37 } 38 int main () 39 { 40 while (~scanf ("%d",&k) && k) 41 { 42 int a, b; 43 scanf ("%d%d",&m, &n); 44 memset (partner, 0, sizeof partner); 45 while (k--) 46 { 47 scanf("%d%d",&a, &b); 48 partner[a][b] = 1; 49 } 50 Hungary(); 51 } 52 return 0; 53 }
2: HDU Matrix
http://acm.hdu.edu.cn/showproblem.php?pid=2119
题意:在一个N*M的矩阵中仅有0,1组成,假设每次都可以消去一行或者一列的全部 1,问你最少要几次把全部的 1 消去?
分析:如果我们以 X 和 Y 坐标来分别表示二分图的 X 部分 Y 部分,把1的X,Y位置连一条线。那么一个点覆盖的意义就是,一次能够消灭的所有1的位置,那么最少点覆盖就是我们要求就的答案了。根据二分图的性质:最小点覆盖 = 最大匹配。所以就转化为了求最大匹配问题。
1 #include <cstdio> 2 #include <cstring> 3 using namespace std; 4 #define N 105 5 int n, m; 6 bool used[N]; 7 int match[N], map[N][N]; 8 9 bool find(int x) 10 { 11 for (int i=1; i<=m; i++) 12 { 13 if (!used[i] && map[x][i]) 14 { 15 used[i] = true; 16 if (match[i]==-1 || find(match[i])) 17 { 18 match[i] = x; 19 return true; 20 } 21 } 22 } 23 return false; 24 } 25 26 void Hungary () 27 { 28 int cnt=0; 29 memset (match, -1, sizeof match); 30 for (int i=1; i<=n; i++) 31 { 32 memset (used, 0, sizeof used); 33 if (find(i)) 34 cnt++; 35 } 36 printf ("%d\n",cnt); 37 } 38 int main () 39 { 40 while (~scanf ("%d",&n) && n) 41 { 42 scanf ("%d",&m); 43 memset (map, 0, sizeof map); 44 for (int i=1; i<=n; i++) 45 for (int j=1; j<=m; j++) 46 scanf ("%d",&map[i][j]); 47 Hungary (); 48 } 49 return 0; 50 }
路径覆盖的定义就是: 在有向图中找一些路径,使之覆盖了图中的所有顶点,就是任意一个顶点都跟那些路径中的某一条关联。
最小路经覆盖 = N - 最大匹配;
3: HDU Air Raid
http://acm.hdu.edu.cn/showproblem.php?pid=1151
题意:在一个仅有单行道的小镇中,每条道路都连接连个不同的交叉口,并且不存在环。现在,需要往这个小镇的交叉路口派一些伞兵,每个在一个路口着陆了的伞兵可以沿着街去到其他路口;问你,最少需要派多少伞兵可以访问这个小镇的所有街道?
分析:题目就是要我们求最小路径覆盖;
1 #include <cstdio> 2 #include <cstring> 3 #define N 150 4 5 bool used[N], map[N][N]; 6 int match[N], n; 7 bool dfs (int x) 8 { 9 for (int i=1; i<=n; i++) 10 { 11 if (!used[i] && map[x][i]) 12 { 13 used[i] = true; 14 if (match[i]==-1 || dfs(match[i])) 15 { 16 match[i] = x; 17 return true; 18 } 19 } 20 } 21 return false; 22 } 23 24 void hungary () 25 { 26 int cnt=0; 27 memset (match, -1, sizeof match); 28 for (int i=1; i<=n; i++) 29 { 30 memset (used, 0, sizeof used); 31 if (dfs(i)) cnt++; 32 } 33 printf ("%d\n",n-cnt); 34 } 35 36 int main () 37 { 38 int t, m, a, b; 39 scanf ("%d",&t); 40 while (t--) 41 { 42 scanf ("%d%d",&n, &m); 43 memset (map, 0, sizeof map); 44 for (int i=0; i<m; i++) 45 { 46 scanf ("%d%d",&a, &b); 47 map[a][b] = 1; 48 } 49 hungary (); 50 } 51 return 0; 52 }
4:HDU Courses
http://acm.hdu.edu.cn/showproblem.php?pid=1083
题意:在一个班上有 N 个学生和 P 门课程,每个人都可以选修 0 门以上的课程,问你是否存在这样一个 P 元素的集合,他满足下面个的关系:
。每个学生都代表一门课程(相当于课代表)
。每门课程都可以在这个集合中找到(每门课都必须有一个课代表)
分析:首先,由于这个集合要满足 p 元素,所以,当 N < P 时,显然,人数不够,必然还剩 P-N 门课找不到人来当课代表,故输出 “NO”;而当 P < N 时,我们按照,题目中提供的以课程为 X 部分,学生为 Y 部分建立二分图,若他们之间存在选修于被选修关系, 则连线,那么一个匹配就是一种要求关系。所以,程序只要求匹配数 M 大于等于 P 即可。不过,这题貌似有漏洞,因为我就算把条件改为 M==P 也能AC。
1 #include <cstdio> 2 #include <cstring> 3 #define N 310 4 5 bool used[N], map[N][N]; 6 int match[N], n, p; 7 bool dfs (int x) 8 { 9 for (int i=1; i<=n; i++) 10 { 11 if (!used[i] && map[x][i]) 12 { 13 used[i] = true; 14 if (match[i]==-1 || dfs(match[i])) 15 { 16 match[i] = x; 17 return true; 18 } 19 } 20 } 21 return false; 22 } 23 24 void hungary () 25 { 26 int cnt=0; 27 memset (match, -1, sizeof match); 28 for (int i=1; i<=p; i++) 29 { 30 memset (used, 0, sizeof used); 31 if (dfs(i)) cnt++; 32 } 33 puts (cnt>=p ? "YES":"NO"); 34 } 35 36 int main () 37 { 38 int t, m, a; 39 scanf ("%d",&t); 40 while (t--) 41 { 42 scanf ("%d%d",&p, &n); 43 memset (map, 0, sizeof map); 44 for (int i=1; i<=p; i++) 45 { 46 scanf ("%d",&m); 47 while (m--) 48 { 49 scanf ("%d",&a); 50 map[i][a] = 1; 51 } 52 } 53 if (n < p) 54 puts ("NO"); 55 else 56 hungary (); 57 } 58 return 0; 59 }
5:POJ Selecting Courses
http://poj.org/problem?id=2239
题意:LM是一个爱学习的人,每次开学的时候,LM总是希望能够在不课程不冲突的情况下选尽量多的课,一周 7 天,每天12节课,共有上百节课可选。老师每次只能上一个班的课,每门课在一个星期内可能会上几次,比如:老师可以在周二给 7 班上数学课,也可以再周三给 12 班上数学课。你可以认为每个班都是一样的,学生可以自用选择任何一个班级来上他喜欢的课。现在,作为他的朋友,请你告诉他他最多可以选多少课??
分析:初一看,这里总共有(课程,时间,班级)共三种关系,而我们现在是要用二分图的最大匹配来解决,显然,我们必须把其中的连个关系合并到一起,由三元组变成二元组,从而建立二分图来解决。经过分析,我们知道,我们最终需要的确定的是,最多有多少课程在上课时间上没有冲突?从而使得学生可以选择没有冲突的课去上,而班级在这里没有什么影响,因为学生是可以任意选择班级的。所以,我们可以把班级和时间合并到一起看成一个关系。那么,我们该怎么合并呢?如果我们直接相加,那么就会出现一个问题:(p=1,q=12)和(p=2,q=11)本来是不同的元素,但若我们直接相加的话,两个值都等于13就表示相同的元素了,所以,我们可以 (p*12+q)来合并来避免上述问题。
1 #include <cstdio> 2 #include <cstring> 3 using namespace std; 4 #define N 400 5 bool used[N], map[N][N]; 6 int match[N], n; 7 8 bool dfs (int x) 9 { 10 for (int i=1; i<=97; i++) 11 { 12 if (!used[i] && map[x][i]) 13 { 14 used[i] = true; 15 if (match[i]==-1 || dfs(match[i])) 16 { 17 match[i] = x; 18 return true; 19 } 20 } 21 } 22 return false; 23 } 24 25 void hungary () 26 { 27 memset (match, -1, sizeof match); 28 int cnt=0; 29 for (int i=1; i<=n; i++) 30 { 31 memset (used, 0, sizeof used); 32 if (dfs (i)) cnt++; 33 } 34 printf ("%d\n", cnt); 35 } 36 37 int main() 38 { 39 int p, q, m; 40 while (~scanf ("%d",&n)) 41 { 42 memset (map, 0, sizeof map); 43 for (int i=1; i<=n; i++) 44 { 45 scanf ("%d",&m); 46 while (m--) 47 { 48 scanf ("%d%d",&p, &q); 49 map[i][p*12+q] = 1; 50 } 51 } 52 hungary (); 53 } 54 return 0; 55 }
6: HDU Girls and Boys
http://acm.hdu.edu.cn/showproblem.php?pid=1068
题意:在大二的时候,有些人就开始恋爱了,所谓的恋爱就是指异性之间的。给你一组数据之间的关系,问你这里面最多有多少人没有谈恋爱??
分析:我们很容易按照给出的数据建图,最大独立集就是我们要求的,在二分图中,我们有:最大独立集 = V - 最大匹配 M;这题需要注意的是,虽然,二分图的模型很明确,但是划分关系部明确。因此,我们建图的时候建成无向边,得到的最大匹配为M,最大独立集为 S ,则有: S = V — M/2;
1 #include <cstdio> 2 #include <vector> 3 #include <cstring> 4 using namespace std; 5 #define N 520 6 7 int n, match[N]; 8 bool used[N],map[N][N]; 9 10 bool dfs(int x) 11 { 12 for (int i=0; i<n; i++) 13 { 14 if (!used[i] && map[x][i]) 15 { 16 used[i] = true; 17 if (match[i]==-1 || dfs(match[i])) 18 { 19 match[i] = x; 20 return true; 21 } 22 } 23 } 24 return false; 25 } 26 27 void hungary () 28 { 29 int cnt=0; 30 memset (match, -1, sizeof match); 31 for (int i=0; i<n; i++) 32 { 33 memset (used, 0, sizeof used); 34 if (dfs(i)) cnt++; 35 } 36 printf ("%d\n",n-cnt/2); 37 } 38 39 int main() 40 { 41 int x, y, m; 42 while (~scanf ("%d",&n)) 43 { 44 memset (map, 0, sizeof map); 45 for (int i=0; i<n; i++) 46 { 47 scanf ("%d: (%d)",&x, &m); 48 while (m--) 49 { 50 scanf ("%d",&y); 51 map[x][y] = map[y][x] = true; 52 } 53 } 54 hungary (); 55 } 56 return 0; 57 }
7: POJ The Perfect Stall
http://poj.org/problem?id=1274
题意:农民FJ上周兴建了一个产奶仓,由于种种原因,某些奶牛只愿在某些摊位产奶,而另一些奶牛只愿在另一些摊位产奶。而一个摊位最多只能容一个奶牛产奶,同样一个奶牛只能占一个摊位。问你最大能使多少奶牛产奶?
分析:直观的二分图模型求最大匹配问题,划分关系也很明确。
1 #include <cstdio> 2 #include <cstring> 3 #define N 220 4 5 bool used[N], map[N][N]; 6 int match[N], n, m; 7 8 bool dfs (int x) 9 { 10 for (int i=1; i<=m; i++) 11 { 12 if (!used[i] && map[x][i]) 13 { 14 used[i] = true; 15 if (match[i]==-1 || dfs(match[i])) 16 { 17 match[i] = x; 18 return true; 19 } 20 } 21 } 22 return false; 23 } 24 25 void hungary () 26 { 27 int cnt=0; 28 memset (match, -1, sizeof match); 29 for (int i=1; i<=n; i++) 30 { 31 memset (used, 0, sizeof used); 32 if (dfs(i)) cnt++; 33 } 34 printf ("%d\n",cnt); 35 } 36 37 int main() 38 { 39 int a, b; 40 while (~scanf ("%d%d",&n, &m)) 41 { 42 memset (map, 0, sizeof map); 43 for (int i=1; i<=n; i++) 44 { 45 scanf ("%d",&a); 46 while (a--) 47 { 48 scanf ("%d",&b); 49 map[i][b] = 1; 50 } 51 } 52 hungary (); 53 } 54 return 0; 55 }
8: POJ Asteroids
http://poj.org/problem?id=3041
题意:Bessie 想要驾驶他的宇宙飞船通过一片危险的行星场,为了安全,飞船必须避开行星。幸运的是,有一种超能武器可以使行星在一秒内蒸发,由于这个武器很贵,所以,问你最少的用多少这种武器才能消灭掉行星?
分析:这个题,如果他是以矩阵的形式给出行星的位置,那么我们可能还不知道怎么建图,不过,数据给出的方式却给了我们一个建图方法,以行星坐标(X,Y)来分别作为二分图的两部分来建。这样的话,最小点覆盖就刚好是我们所要求的。
1 #include <cstdio> 2 #include <cstring> 3 #define N 550 4 5 bool used[N], map[N][N]; 6 int match[N], n, m; 7 8 bool dfs (int x) 9 { 10 for (int i=1; i<=n; i++) 11 { 12 if (!used[i] && map[x][i]) 13 { 14 used[i] = true; 15 if (match[i]==-1 || dfs(match[i])) 16 { 17 match[i] = x; 18 return true; 19 } 20 } 21 } 22 return false; 23 } 24 25 void hungary () 26 { 27 int cnt=0; 28 memset (match, -1, sizeof match); 29 for (int i=1; i<=n; i++) 30 { 31 memset (used, 0, sizeof used); 32 if (dfs(i)) cnt++; 33 } 34 printf ("%d\n",cnt); 35 } 36 37 int main() 38 { 39 int a,b; 40 while (~scanf ("%d%d",&n, &m)) 41 { 42 memset (map, 0, sizeof map); 43 for (int i=0; i<m; i++) 44 { 45 scanf ("%d%d",&a, &b); 46 map[a][b] = 1; 47 } 48 hungary (); 49 } 50 return 0; 51 }
9: POJ Guardian of Decency
http://poj.org/problem?id=2771
题意:FS是一所高中的老师,他想要带学生出去远足,但是他又担心在路上,同学们会发生恋爱关系,不过,据他观察发现,如果两人之间满足下面条件之一的就基本上不会有恋爱关系:
1.两人身高差超过40cm; 2.两人是同性; 3.两人爱好的音乐风格不同; 4.两人有相同的运动爱好。
现在,FS 想带一群人去远足,但是,那群人里面任意两个人都不能有发生恋爱的可能。问你,他最多能带多少人?
分析:显然,我们可以分别以男女为二分图的两部来建图,不过,我们要是以不发生恋爱关系的人之间建边的话,最大匹配并没有意义。所以,我们考虑在两个可能恋爱的之间建边的话,最大独立集就是我们所要求的了。
1 #include <cstdio> 2 #include <cstring> 3 #define N 505 4 5 struct Node 6 { 7 int h; 8 char sex[2], mus[105], spr[105]; 9 }node[N]; 10 bool used[N], map[N][N]; 11 int match[N], n; 12 int fabs (int x) 13 { 14 return x < 0 ? -x:x; 15 } 16 17 bool dfs (int x) 18 { 19 for (int i=0; i<n; i++) 20 { 21 if (!used[i] && map[x][i]) 22 { 23 used[i] = true; 24 if (match[i]==-1 || dfs(match[i])) 25 { 26 match[i] = x; 27 return true; 28 } 29 } 30 } 31 return false; 32 } 33 34 void hungary () 35 { 36 int cnt=0; 37 memset (match, -1, sizeof match); 38 for (int i=0; i<n; i++) 39 { 40 memset (used, 0, sizeof used); 41 if (dfs(i)) cnt++; 42 } 43 printf ("%d\n",n-cnt); 44 } 45 46 int main () 47 { 48 int t; 49 scanf ("%d",&t); 50 while (t--) 51 { 52 scanf ("%d",&n); 53 for (int i=0; i<n; i++) 54 scanf("%d%s%s%s",&node[i].h, node[i].sex,node[i].mus, node[i].spr); 55 memset (map, 0, sizeof map); 56 for (int i=0; i<n-1; i++) 57 for (int j=i+1; j<n; j++) 58 { 59 if (fabs(node[i].h-node[j].h)<=40 && node[i].sex[0]!=node[j].sex[0] && 60 strcmp(node[i].mus,node[j].mus)==0 && strcmp(node[i].spr,node[j].spr)) 61 { 62 if(node[i].sex[0]=='M') 63 map[i][j] = 1; 64 else 65 map[j][i] = 1; 66 } 67 } 68 hungary (); 69 } 70 return 0; 71 }
10: HDU Machine Schedule
http://acm.hdu.edu.cn/showproblem.php?pid=1150
题意:有两台机器 A 和 B 各有N和M种工作模式,现在给你K份任务,每份任务都可以在A机器的模式 i 或B机器的模式 j 下独立完成。并且K份任务可以按任意顺序完成。不过每次切换模式的时候,都得花时间手动重启,问你,完成所有任务最少的重启次数是多少?
分析:首先,题目给出了(i,x,y)三元组的关系,若我们刚开始着眼于任务 i不放,一直试图让任务 i 和模式 x,y建立一个二分图,显然,若各自独立与X,y建图,这就不是二分图,而是三分图了。若我们把x,y加起来建图,那么模式切换操作将无法体现出来。仔细看题,我们发现,整个过程只有A B两种机器,刚好符合二分图的构造。若我们在(i,x,y)的关系中选择x-y来建边。那么一个匹配刚好(也一定是)是完成某个任务 Ki 在机器A 和机器B的模式(也就是一个匹配==一个任务)。图中任一个定点都是一个模式,而我们所求的就是用最少的顶点(模式)覆盖所的边(完成所有任务)。
1 #include <cstdio> 2 #include <cstring> 3 using namespace std; 4 #define N 110 5 6 int n, m, k; 7 bool used[N], map[N][N]; 8 int match[N]; 9 10 bool dfs(int x) 11 { 12 for (int i=1; i<=m; i++) 13 { 14 if (!used[i] && map[x][i]) 15 { 16 used[i] = true; 17 if (match[i]==-1 || dfs(match[i])) 18 { 19 match[i] = x; 20 return true; 21 } 22 } 23 } 24 return false; 25 } 26 27 void hungary () 28 { 29 int cnt=0; 30 memset (match, -1, sizeof match); 31 for (int i=1; i<=n; i++) 32 { 33 memset (used, 0, sizeof used); 34 if (dfs(i)) cnt++; 35 } 36 printf ("%d\n", cnt); 37 } 38 int main() 39 { 40 int a, x, y; 41 while (~scanf("%d",&n) && n) 42 { 43 scanf ("%d%d",&m, &k); 44 memset (map, 0, sizeof map); 45 for (int i=0; i<k; i++) 46 { 47 scanf ("%d%d%d",&a,&x,&y); 48 map[x][y] = 1; 49 } 50 hungary (); 51 } 52 return 0; 53 }