二分匹配
月老的难题:
输入:
每组测试数据的第一行有两个整数n,K,其中男孩的人数与女孩的人数都是n。(n<=500,K<=10 000)
随后的K行,每行有两个整数i,j表示第i个男孩与第j个女孩有可能结成幸福的家庭。(1<=i,j<=n)
输出:
输出最多可能促成的幸福家庭数量
[cpp] view plaincopy
1 # include<stdio.h>
2 # include<string.h>
3 # include<list>
4 # include<vector>
5 using namespace std;
6
7 vector<int> s[505];
8 int vis[505], link[505];
9
10 bool get_num(int i)
11 {
12 for(int j = 0; j < s[i].size(); j++) //指向第一个元素和最后一个元素
13 {
14 int t = s[i][j];
15 if(vis[t] == 0) //用*取值
16 {
17 vis[t] = 1;
18 if(link[t] == 0 || get_num(link[t]) )
19 {
20 link[t] = i;
21 return true;
22 }
23 }
24 }
25 return false;
26 }
27
28 int main()
29 {
30 int t, n, k;
31 scanf("%d", &t);
32 while(t--)
33 {
34 scanf("%d%d", &n, &k);
35 int a, b;
36 for(int i = 1; i <= n; i ++) //用之前清空,以免错误和忘记
37 {
38 s[i].clear();
39 }
40 for(int i = 0; i < k; i ++ )
41 {
42 scanf("%d%d", &a, &b);
43 s[a].push_back(b); //链表中放元素
44 }
45 memset(link, 0, sizeof(link));
46 int num=0;
47 for(int i = 1; i <= n; i ++)
48 {
49 memset(vis, 0, sizeof(vis)); //每次都必须初始化
50 if(get_num(i))
51 num++;
52 }
53 printf("%d\n",num);
54 }
55 return 0;
56 }
57
二分图:
二分图又称二部图,是图论中的一种特殊模型。设G=(V,E)是一个无向图,如果顶点V可以分割为两个互不相交的子集(A,B),并且图中的每条边 (i,j)所关联的两个顶点i和j分别属于这两个不同的顶点集(i in A, j in B), 则称图G是二分图。e.g.
匹配:
给定一个二分图,在G的一个子图G’中,如果G’的边集中的任意两条边都不依附于同一个顶点,则称G’的边集为G的一个匹配
最大匹配:
在所有的匹配中,边数最多的那个匹配就是二分图的最大匹配了
顶点覆盖:
在顶点集合中,选取一部分顶点,这些顶点能够把所有的边都覆盖了。这些点就是顶点覆盖集
最小顶点覆盖:
在所有的顶点覆盖集中,顶点数最小的那个叫最小顶点集合。
独立集:
在所有的顶点中选取一些顶点,这些顶点两两之间没有连线,这些点就叫独立集
最大独立集:
在左右的独立集中,顶点数最多的那个集合
路径覆盖:
在图中找一些路径,这些路径覆盖图中所有的顶点,每个顶点都只与一条路径相关联。
最小路径覆盖:
在所有的路径覆盖中,路径个数最小的就是最小路径覆盖了。
熟悉了这些概念之后,还有一个二分图最大匹配的König定理,这个定理的内容是:最大匹配 = 最小顶点覆盖。此处不证明其正确性。有了这个定理之后还可以得出一些二分图特有的公式:
最大独立集 = 顶点个数 – 最小顶点覆盖(最大匹配)
这个公式,我们可以利用最大匹配来找到最大的独立集。而最大独立集和最小路径覆盖有个千丝万缕的关系。
对于二分图的最大匹配,常用的求解方法是hungarian算法和最大流算法。以poj上的题目为例说明:
POJ2271: 题目大意是,一群男孩和女孩共N人,某些男孩和女孩之间会发生恋爱关系(满足一定的关系),现在希望找到最多的孩子,他们之间不会发生恋爱关系。
分析:找到最多的孩子,没有恋爱关系,这实质上是找最大独立集。假设男孩在左有a个,女孩在右有b个,那么如果某男孩和某女孩之间有关系,就连线。最大独 立集就是找到最多顶点,顶点之间没有联系,正好就是所求,而最大独立集就是 N-最大匹配,所以问题得到解决。试想,如果二分图中没有连线,那么所有的孩子都可选,最大独立集也是N,他们是等价的。如果存在一条连线,那么去掉一个 孩子就是所找的孩子,最大独立集此时是N-1.依次类推。在试想,最大匹配其实就找到了几对恋爱对象,假设是这样的
a b
B0 G0
B1 G1
… …
Bi Gi
… …
我们只需要把他们的另一半去掉,就是我们找的孩子。不过会有这样的疑问,如果我取出了B1的另一半G1,B1会不会和其余的孩子恋爱呢,比如说B1和b会 恋爱,那么好吧,去掉G1的另一半B1,这样就不会有问题了吧。还有担心?G1会不会和其余的孩子恋爱呢,比如说G1和a会恋爱,不过这样的情况是不会出 现的。假如G1和a好,B1和b好,那么最大匹配中出现的是两条边G1->a B1->b,而不是现在的B1和G1.所以,既然最大匹配中选择了B1和G1,去掉他们中间的一个肯定是可行的。所以答案就是N-最大匹配了。
POJ3692:题目大意是,一群男孩B个(他们互相认识),一群女孩G个(他们互相认识),某些男孩和某些女孩相识,现在找出最多的孩子,他们互相认识
分析:这个题目和上面的有些相似。对于利用二分图最大匹配算法解题最重要不是匹配算法本身,而是如何问题转化为二分图模型。一旦模型建立,就很容易了。题 目要找的是一群孩子,他们之间都互相认识,也就是说这是一个团(图的概念,任意两个顶点之间都有连线)。可是如果直接去找团,可能比较麻烦。因为这是二分 图,自然要利用二分图的性质。在二分图的算法里面没有找团的相关算法,所以我们可以考虑反问题,找出最多的孩子,他们之间互不认识,这不是就是求最大独立 集嘛。建立这样的二分图,左边是男孩,右边是女孩,如果男孩和女孩不认识就连上边,在这样的二分图中,找最大独立集,其实就是找出所有的相互认识的孩子 了。接下来就很容易了。此题说明模型的转化和构图很重要。
POJ3041:题目大意是,一个矩阵,某些位置有小行星,有一种炸弹,一次可以炸掉一行或者一列,现在问题是需要最少用多少这样的炸弹。
分析:模型转化,非常巧妙的利用二分图来解决。利用二分图必须有左顶点和右顶点,我们把行作为左顶点,列作为右顶点,如果该行和该列的交点有小行星,就连 线。求此二分图的最大匹配就是了。对这个问题展开思考,为什么可以这么转化。其实从最小顶点覆盖的角度来想比较好理解,左边的顶点和右边的顶点只有当有小 行星的时候才有连线,那么只要找到最少的顶点把所有的边覆盖了,那么就是所求的解了。最小顶点覆盖等值于最大匹配
POJ1466:题目大意是,一群人N,某人可能是多某些人有罗曼史,性别未知,但一定是异性。找出最多的同学,他们之间无罗曼史
分析:因为性别未知,所以可以把所有的人当成左顶点,右边也是所有的人,建立二分图,可以想象,这样求出来的最大匹配是男女分开建立的二分图的最大匹配的二倍。而题目让找最大独立集,所以应该是N-最大匹配/2;
POJ1325:题目大意是,有两台机器,有多个任务,每个任务都可在这两台机器上运行,不过不同的模式需要重启电脑,很浪费时间,现在要找出最好的调度方式,减少调度时间。
(在某个点上能覆盖完成多少任务,能一起在一个点上完成,就让在一个点上完成)
58 题意:有A,B两台机器, 机器A 有 n个模式(0, 1, 2....n-1),同样机器B有m个模式, 两个机器一开始的模式都为0,有k个作业(id,x,y) 表示作业编号id, 该作业必须在A机器在模式x下或者B机器在模式y下完成,问你至少要切换几次机器模式。
思路:很裸的最小覆盖点集,不熟悉概念的多看看蓝书吧,很容易证明 最小覆盖点集 == 最大匹配int main() {
59 int i;
60 while( ~scanf("%d", &n) && n) {
61 scanf("%d%d", &m, &q);
62 int x, y;
63 for(i = 0; i < n; i++)
64 edge[i].clear();
65 while(q--) {
66 scanf("%*d%d%d", &x, &y);
67 if(!x || !y) continue;
68 edge[x].push_back(y);
69 }
70 memset(pre, -1, sizeof(int)*m);
71 int cnt = 0;
72 for(i = 0; i < n; i++) {
73 memset(vis, 0, sizeof(bool)*m);
74 if(dfs(i)) cnt++;
75 }
76 printf("%d\n", cnt);
77
78 }
79 return 0;
80 }
分析:最少的顶点覆盖最多的边(任务),所以是最小顶点覆盖问题
POJ2060:题目大意,有很多人预订出租车,如果出租车做完一个任务能够敢到下一个任务,就不需要在调度一辆出租车了,现在请问最少需要几辆出租车。
分析:最小路径问题,对任务构图,将一个任务拆开成两个点,建立二分图,如果一个任务能够完成之后赶到下个任务就连线,然后就是二分图问题了。最小路径等值于二分图的最大独立集
81 int can(int s,int e) /*判断两个任务间是否能连线*/
82 {
83 int dis=p[s].h*60+p[s].m;
84 dis+=abs(p[s].x1-p[s].x2)+abs(p[s].y1-p[s].y2);
85 dis+=abs(p[e].x1-p[s].x2)+abs(p[e].y1-p[s].y2);
86 if(dis<p[e].h*60+p[e].m)
87 return 1;
88 return 0;
89 }
90
91 for(i=1;i<=n;i++)
92 {
93 scanf("%d:%d",&p[i].h,&p[i].m); //建图
94 scanf("%d%d%d%d",&p[i].x1,&p[i].y1,&p[i].x2,&p[i].y2);
95 }
96 for(i=1;i<=n;i++)
97 for(j=i+1;j<=n;j++)
98 if(can(i,j))
99 map[i][j]=1;
POJ2226:和3041相似,不过这里不是销毁一行或者一列,一次只能销毁连着的一行或者一列。可以把所有的行连续的段拿出来作为左顶点,所有的列连续的段拿出来作为右顶点,如果左段与右段之间有相交,就连线。然后求最小顶点覆盖
100 nx=0;
101 ny=0;
102 for(i=1;i<=n;i++)
103 {
104 j=1;
105 while(j<=m)
106 {
107 if(s[i][j]=='*')
108 {
109 nx++;
110 while(s[i][j]=='*')
111 {
112 cx[i][j]=nx;
113 j++;
114 }
115 }
116 else j++;
117 }
118 }
119 for(j=1;j<=m;j++)
120 {
121 i=1;
122 while(i<=n)
123 {
124 if(s[i][j]=='*')
125 {
126 ny++;
127 while(s[i][j]=='*')
128 {
129 cy[i][j]=ny;
130 i++;
131 }
132 }
133 else i++;
134 }
135 }
136 for(i=1;i<=n;i++)
137 {
138 for(j=1;j<=m;j++)
139 {
140 if(s[i][j]=='*')
141 {
142 map[cx[i][j]][cy[i][j]]=1;
143 }
144 }
145 }
POJ1422:最小路径覆盖
POJ2594:特殊的最小路径覆盖,每个顶点可以有多条路径经过,这时需要事先把任意两点之间是否能够到达求出,然后在求路径覆盖。
POJ1548:最小路径覆盖
POJ3216:最小路径覆盖
二分匹配的补充
最新推荐文章于 2024-08-25 22:01:17 发布