题目意思很简单,一个矩阵中用若干个0和1,你一个可以把某一行,或者某一列的1消掉,问你至少要消几次,才能把所有的1全部消除。
二分图匹配的典型例题,不过说话,匈牙利匹配和二分图实在是不好理解,我也看了两三天,才琢磨个差不多。。。希望跟我一样到了大三的朋友们不要放弃ACM,不要堕落下去,多看看,总会看懂的。继续讲题目。把矩阵中每一行看成是二分图左边的一部分,每一列看成是二分图右边的一部分。
看测试数据:
3 3 0 0 0 1 0 1 0 1 0第1行0列,1行2列,2行1列是1,那么变成二分图就是上面那幅图。(画的很挫。。。。)![]()
现在我们的目的是将所有的边消除对不?1行0列是1,我们就要消除1到0这条边,1行2列是1,我们就要消除1到2这条边。但是我们现在一次只能消除一行或者一列,也就是说,我们一次只能消除一个点,比如消除第一行,就是把左边那部分标号为1的点抹掉,只要这个店没了,那么跟她相关的边也没了。现在问题变成了,如何消除最少数量的点,来把所有的边去掉。假设我现在抹掉了左边的点1,毫无疑问,1到0,1到2这两条边都没了。再把左边的点2抹点,2到1这条边也没了,同时,这种方式也是消除最少的点来达到效果的方式。加入你先抹掉右边的点0,那结果就是3了,不满足最小的要求。现在模型转化成了如果用最少的点去覆盖所有的边,这个叫做最小覆盖点。最小覆盖点 = 最大匹配,这是个结论,记得就是啦,要证明的话,网上也有很多。然后现在就来求最大匹配吧。不得不说,匈牙利匹配真让人蛋疼。。。网上大部分的就都只说个大概,一个“显然”,就够让你研究两三天。下面直接贴代码吧。#include<stdio.h> #include<memory> using namespace std; int n,m,tag[105],pre[105]; //邻接表建图。。。。第一次用。。写的不好 struct node { int v; node *next; node(int i) { v = i; next = 0; } }*map[105]; bool hungary(int k)//匈牙利匹配 { node *p = map[k] -> next; //p表示与节点K相连的边 while(p)//这。。。就不需要解释了吧。。。链表。。 { int v = p -> v; if(!tag[v]) //如果节点v没被接上, { tag[v] = 1;//那就让这个点加入匹配吧。 if(pre[v] == -1 || hungary(pre[v]))//如果这个点没有和左边的图中任何一个点相连,那么就让k匹配这个点 { //或者,跟pre【v】,也就是跟v相连的,属于左边那个图的点商量一下,看看左边那个图的点能不能换个点匹配 pre[v] = k;//能的话,就让k匹配现在这个v return 1; } } p = p -> next; } return 0; } int main() { int a; while(scanf("%d",&n),n) { scanf("%d",&m); for(int i = 0;i < n;++i) map[i] = new node(i); memset(pre,-1,sizeof(pre)); for(i = 0;i < n;++i) for(int j = 0;j < m;++j) { scanf("%d",&a); if(a) { node *p = map[i] -> next; //邻接表建图,用链表的头插法。 map[i] -> next = new node(j); map[i] -> next -> next = p; } } int ans = 0; for(i = 0;i < n;++i) { if(map[i] -> next) { memset(tag,0,sizeof(tag)); ans += hungary(i); } } printf("%d\n",ans); } return 0; }