题意:一个矩形——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; }