UVA 11419 SAM I AM(二分图最大点覆盖)

题目链接:

PDF

题意:

有一个矩阵,有一些点上有怪,现在一发炮可以清一整行或者一整列怪,求最少需要多少炮清光所有怪

思路:

将其转化为二分图最小点覆盖,即把一行或一列看成点,有怪的地方把行和列连边。
重点是输出方案,靠自己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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值