Uva 11419 SAM I AM (最小点覆盖)

n*m的网络上放了一些目标,要求用最少的炮弹打掉。炮弹沿水平或垂直方向飞行,可以一次打掉该水平或垂直方向上所有目标。


建图:

可以发现,对于一个目标的坐标(x,y),只要x有炮弹,或者y有炮弹就可以被打掉。那么,将每一行看做一个节点,每一列看做一个节点(或者说将目标视为一条边,两个顶点分属于不同集合),对于每一个目标(x,y),将行x与列y连一条边,便得到一个二分图,则问题转化为最小点覆盖问题。


利用匈牙利算法求出最大匹配

根据König定理,最小点覆盖数=最大匹配数。

再根据上面的定理可以求出覆盖的点。即方案。

注意到上面的定理中,是从右侧开始标记顶点。通常我们是从左侧的点开始寻找增广路,同样地,可以从左侧标记顶点,最后左侧没有被标记的、右侧被标记的点就是覆盖的点。


#include<bits/stdc++.h>
using namespace std;
#define N 1005
int n,m,M[N][N],Left[N],Right[N],S[N],T[N];
vector<int>X,Y;
bool match(int x){
    S[x]=1;
    for(int i=1;i<=m;++i)
        if(M[x][i]&&!T[i]){
            T[i]=1;
            if(!Left[i]||match(Left[i])){
                Left[i]=x;
                Right[x]=i;
                return 1;
            }
        }
    return 0;
}
int main()
{
    int i,x,y,k;
    while(~scanf("%d%d%d",&n,&m,&k)){
        if(!m&&!k&&!n) break;
        memset(M,0,sizeof(M));
        for(i=1;i<=k;++i){
            scanf("%d%d",&x,&y);
            M[x][y]=1;
        }
        memset(Right,0,sizeof(Right));
        memset(Left,0,sizeof(Left));
        int ans=0;
        for(i=1;i<=n;++i){
            memset(S,0,sizeof(S));
            memset(T,0,sizeof(T));
           if(match(i)) ++ans;
        }
        printf("%d",ans);

        memset(S,0,sizeof(S));
        memset(T,0,sizeof(T));
        X.clear(),Y.clear();

        for(i=1;i<=n;++i) if(!Right[i]) match(i);
        for(i=1;i<=n;++i) if(!S[i]) X.push_back(i);
        for(i=1;i<=m;++i) if(T[i]) Y.push_back(i);

        for(i=0;i<X.size();++i) printf(" r%d",X[i]);
        for(i=0;i<Y.size();++i) printf(" c%d",Y[i]);
        puts("");
    }
    return 0;
}


你可能感兴趣的:(Uva 11419 SAM I AM (最小点覆盖))