Uva - 11419 - SAM I AM

题意:一个矩形——R*C的网格,在某些位置上有石头,在网格外开一炮可以打掉该行或者该列的石头,求打掉这些石头最少需要多少门大炮,位置分别设在哪行哪列(0<R<1001, 0 < C < 1001)。

题目链接:http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=2414

——>>以每行为X结点,每列为Y结点,有石头的点的横纵坐标关系为边建立二分图,那么,对于一个石头的位置,就是一条边,只要在这条边2个端点中的1个设大炮就可打掉这个石头,这正正是二分图的最小点覆盖。

对于具体的位置,则可从X中所有的未盖点出发,寻找增广路,并且标记沿途经过的结点,那么X中未被标记的点和Y中被标记的点即为所求,这是因为:

从X中的未盖点出发,终点一定在X结点(否则存在增广路,与最大匹配矛盾),那么这次增广中标记Y结点的数量就会比标记X结点的数量少1,故取已标记的Y结点用的大炮就会比取已标记的X结点的数量要少1。

对于X中未被标记的结点a,首先a是已盖点,Y中必有结点b与之匹配,且b一定不和X中的未盖点相连(否则a,b一定被标记了),若a还与Y中的未盖点相连,则应取a,而不是b及与a相连的Y中的未盖点,这样只在a一个点设大炮就可解决掉这几个石头;若a连的还有Y中的已盖点,那么那点由它的匹配点去处理,a不用去处理它;若a不与Y的其它点相连,那么取a点或者取b都行,我取了a点,所以这些情况取已盖点a就是。

综上可得取X中未被标记的点和Y中被标记的点就是设大炮的行与列。

另外,用邻接表会比用简单的邻接矩阵快10倍!微笑

 

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int maxn = 1000 + 10;
const int maxm = 1000000 + 10;

int R, C, fa[maxn], mo[maxn], v[maxm], head[maxn], nxt[maxm], ecnt;
bool S[maxn], T[maxn];

void init(){
    memset(head, -1, sizeof(head));
    ecnt = 0;
}

void addEdge(int uu, int vv){
    v[ecnt] = vv;
    nxt[ecnt] = head[uu];
    head[uu] = ecnt;
    ecnt++;
}

bool match(int i){
    S[i] = 1;
    for(int e = head[i]; e != -1; e = nxt[e]) if(!T[v[e]]){
        T[v[e]] = 1;
        if(!fa[v[e]] || match(fa[v[e]])){
            fa[v[e]] = i;
            return 1;
        }
    }
    return 0;
}

void solve(){
    memset(fa, 0, sizeof(fa));
    int ret = 0;
    for(int i = 1; i <= R; i++){
        memset(S, 0, sizeof(S));
        memset(T, 0, sizeof(T));
        if(match(i)) ret++;
    }
    memset(mo, 0, sizeof(mo));
    memset(S, 0, sizeof(S));
    memset(T, 0, sizeof(T));
    for(int i = 1; i <= C; i++) mo[fa[i]] = 1;
    for(int i = 1; i <= R; i++) if(!mo[i]) match(i);
    printf("%d", ret);
    for(int i = 1; i <= R; i++) if(!S[i]) printf(" r%d", i);
    for(int i = 1; i <= C; i++) if(T[i]) printf(" c%d", i);
    puts("");
}

int main()
{
    int N, x, y;
    while(scanf("%d%d%d", &R, &C, &N) == 3){
        if(!R && !C && !N) return 0;
        init();
        for(int i = 1; i <= N; i++){
            scanf("%d%d", &x, &y);
            addEdge(x, y);
        }
        solve();
    }
    return 0;
}


 


 

posted on 2013-09-06 21:07  you Richer  阅读(274)  评论(0编辑  收藏  举报