UVA 11419 SAM I AM

26 篇文章 0 订阅
15 篇文章 0 订阅

UVA 11419 SAM I AM

二分图最小点覆盖,输出方案

题目

给出一个R×C的网格,网格上棉纺了一些目标。可以在网格外发射子弹,子弹会沿着垂直或水平方向飞行,并且打掉飞行路径上的所有目标。你的任务是计算出最少需要多少子弹,各从哪个位置发射,才能把所有目标全部打掉。

思路

二分图最大匹配的König定理及其证明

König定理:最小覆盖数等于最大匹配数。把目标所在的坐标,转化为XY结点,行看成X结点,列看成Y结点。那现在问题就变成了,如何选最少的结点,覆盖所有的边。

求最小覆盖的步骤大致如下:1)在右边找到一个未被匹配过的点,标记。2)走一条没被匹配过的边,到左边的点,标记。3)走一条匹配过的边到右边,标记。4)重复2,3步骤直到不能再走。5)回到步骤一,直到找不到未被匹配且未被标记的右边的点。6)标记结束后,右边没有标记的点,和左边标记过的点,就可以覆盖所有的边。

左边右边是等价的,根据自己习惯命名就好

代码

#include<bits/stdc++.h>
#include<stdlib.h>
#define M(a,b) memset(a,b,sizeof(a))
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
using namespace std;
const int MAXN=1007;
const int oo=0x3f3f3f3f;
typedef long long LL;

struct BPM
{
    int n, m;
    vector<int > G[MAXN];
    int left[MAXN];
    int right[MAXN];
    bool T[MAXN];
    bool S[MAXN];

    void init(int n,int m)
    {
        this->n = n;
        this->m = m;
        for(int i = 1; i <= n; i++) G[i].clear();
    }

    void addEdge(int u, int v)
    {
        G[u].push_back(v);  //建边
    }

    bool match(int u)
    {
        S[u] = true; //标记右边的点u
        for(int i = 0; i < G[u].size(); i++)  //遍历由u点出发,连接的左边的点
        {
            int v = G[u][i];
            if(!T[v])   //左边的没标记过的点, 走没匹配过的边
            {
                T[v] = true;
                if(left[v] == -1 || match(left[v]))  //走匹配过的边到右边的点
                {
                    left[v] = u;
                    right[u] = v;
                    return true;
                }
            }
        }
        return false;
    }

    int solve()
    {
        memset(left, -1, sizeof(left));
        memset(right, -1, sizeof(right));
        int ans = 0;
        for(int u = 1; u <= n; u++)
        {
            memset(S, 0, sizeof(S));
            memset(T, 0, sizeof(T));
            if(match(u)) ans++;  //先用匈牙利算法求出最大匹配
        }
        return ans;
    }

    int mincover(vector<int>& X, vector<int>& Y)//求解最小覆盖方案
    {
        int ans = solve();
        memset(S, 0, sizeof(S));
        memset(T, 0, sizeof(T));
        for(int u = 1; u <= n; u++) //在右边的点集找到一个未被标记的点
            if(right[u] == -1) match(u); //从这个未标记的点开始走增广路
        for(int u = 1; u <= n; u++)
            if(!S[u]) X.push_back(u); //标记结束之后,记录右边没标记的点
        for(int v = 1; v <= m; v++)
            if( T[v]) Y.push_back(v);  //记录左边标记过的点
        return ans;
    }
} bpm;

int main()
{
    int n,m,k;
    while(scanf("%d%d%d",&n,&m,&k)==3)
    {
        if(n==0&&m==0&&k==0) break;
        bpm.init(n,m);
        while(k--)
        {
            int a,b;
            scanf("%d%d",&a,&b);
            bpm.addEdge(a,b);
        }
        vector<int> X,Y;
        X.clear(),Y.clear();
        int res=bpm.mincover(X,Y);
        printf("%d",res);
        for(int u:X)
        {
            printf(" r%d",u);
        }
        for(int v:Y)
        {
            printf(" c%d",v);
        }
        printf("\n");
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值