题目链接:
题意:
有一个矩阵,有一些点上有怪,现在一发炮可以清一整行或者一整列怪,求最少需要多少炮清光所有怪
思路:
将其转化为二分图最小点覆盖,即把一行或一列看成点,有怪的地方把行和列连边。
重点是输出方案,靠自己YY是很难想出来的,那我们来看《算法竞赛进阶指南》里的做法:
1. 求出二分图最大匹配
2. 从左侧每个非匹配点出发,再做一遍DFS找增广路(绝对找不到)
3. 取左部未标记的点和右部被标记的点,即为最小点覆盖
证明这种做法的正确性分为:1. 证他恰好取了最大匹配数个点,2. 证覆盖了所有边。
具体证明不再赘述。
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
const int N = 1010;
int n, m, k;
vector<int> to[N];
int mx[N], my[N], ret;
bool visy[N], visl[N], visr[N];
bool dfs(int u)
{
visl[u] = 1;
for (int i = 0; i < to[u].size(); i++){
int v = to[u][i];
visr[v] = 1;
if (!visy[v]){
visy[v] = 1;
if (my[v] == -1 || dfs(my[v])){
mx[u] = v;
my[v] = u;
return true;
}
}
}
return false;
}
int main()
{
while (scanf("%d%d%d", &n, &m, &k)){
if (n == 0 && m == 0 && k == 0) break;
for (int i = 1; i <= n; i++)
to[i].clear();
for (int i = 1; i <= k; i++){
int x, y;
scanf("%d%d", &x, &y);
to[x].push_back(y);
}
memset(mx, -1, sizeof(mx));
memset(my, -1, sizeof(my));
ret = 0;
for (int i = 1; i <= n; i++){
memset(visy, 0, sizeof(visy));
if (dfs(i)) ret++;
}
printf("%d", ret);
memset(visl, 0, sizeof(visl));
memset(visr, 0, sizeof(visr));
for (int i = 1; i <= n; i++)
if (mx[i] == -1){
memset(visy, 0, sizeof(visy));
dfs(i);
}
for (int i = 1; i <= n; i++)
if (!visl[i])
printf(" r%d", i);
for (int i = 1; i <= m; i++)
if (visr[i])
printf(" c%d", i);
printf("\n");
}
return 0;
}