题目链接:http://acm.hust.edu.cn/vjudge/problem/73800
题意:给出若干行01串,每次可以消掉一行1或一列1,问最少几次能把所有的1消完。
思路: 我们可以把每一行看做X点集中的一个点,每一列看做Y点集中的一个点,如果第i行第j列为1,就从X点集中的对应的点i向Y点集中的点j连一条边。这样问题就转化成了,给出一个二分图,用最少的点覆盖所有的边(一条边被覆盖即这条边至少有一个端点在这些点上),即求最小点覆盖数。
由二分图的性质可知,最小匹配数等于最大点覆盖数。证明如下:设该图最小点覆盖数为X,最大匹配数为M。
(1)首先证明X>=M,因为最大匹配数为M,而这M条边的端点各不相同,仅覆盖这M条边就需要M个点,所以有X>=M。
(2)下面证明X<=M,用反证法,假设X>M,,即若用M个点覆盖这些边,至少有一条边没有被覆盖,那个这条边的两个端点都没有边相连,所以这条边可以使得最大匹配 数加1,这与最大匹配数为M矛盾,所以X<=M.
综上所述,X=M。
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
const int maxn = 100 + 10;
char s[maxn][maxn];
int vis[maxn], from[maxn];
int n,m;
bool solve(int x)
{
for (int i = 0; i < m; i++) {
if (s[x][i] == '0') continue;
if (!vis[i]) {
vis[i] = 1;
if (from[i] == -1 || solve(from[i])) {
from[i] = x;
return true;
}
}
}
return false;
}
int main()
{
int T, kase = 0;
scanf("%d",&T);
while(T--) {
memset(from, -1, sizeof from);
scanf("%d%d",&n,&m);
for (int i = 0; i < n; i++) scanf("%s",s[i]);
int ans = 0;
for (int i = 0; i < n; i++)
if (memset(vis, 0, sizeof vis),solve(i)) ++ans;
printf("Case #%d: %d\n", ++kase, ans);
}
return 0;
}