将行与列分别看成x集和y集,那么每个目标对应一条边,那么就是最小覆盖
构造解,将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中被标记的点就是设大炮的行与列。
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <vector>
#include <map>
#include <cmath>
#include <set>
#include <queue>
using namespace std;
const int INF=1e9+10;
const double EPS = 1e-10;
typedef long long ll;
int g[1005][1005];
int linky[1005],linkx[1005];
int vis[1005],tagx[1005],tagy[1005];
int r,c,n;
bool match(int u){
tagx[u]=1;
for(int i=1;i<=c;i++){
if(g[u][i]&&!vis[i]){
vis[i]=1;
tagy[i]=1;
if(!linky[i]||match(linky[i])){
linky[i]=u;
linkx[u]=i;
return true;
}
}
}
return false;
}
void solve(){
memset(linky,0,sizeof(linky));
memset(linkx,0,sizeof(linkx));
int res=0;
for(int i=1;i<=r;i++){
memset(vis,0,sizeof(vis));
if(match(i)) res++;
}
printf("%d",res );
memset(tagx,0,sizeof(tagx));
memset(tagy,0,sizeof(tagy));
memset(vis,0,sizeof(vis));
for(int i=1;i<=r;i++){
if(!linkx[i]) match(i);
}
for(int i=1;i<=r;i++){
if(!tagx[i]) printf(" r%d",i);
}
for(int i=1;i<=c;i++){
if(tagy[i]) printf(" c%d",i );
}
printf("\n");
}
int main(){
//freopen("out.txt","w",stdout);
while(scanf("%d %d %d",&r,&c,&n)&&n&&r&&c){
memset(g,0,sizeof(g));
for(int i=0;i<n;i++){
int u,v;
scanf("%d %d",&u,&v);
g[u][v]=1;
}
solve();
}
return 0;
}