/**
解题思路:将每一行看作一个X结点,每一列看作一个Y结点,每个目标对应一条边,这样,子弹打掉所有目标意味着每条边至少有一个结点被选中。构造解:借助于匈牙利树,从X中的所有未盖点出发扩展匈牙利树,标记树中的所有点,则X中的未标记点喝Y中的已标记点组成所求的最小覆盖。
*/
题目大意:在R*C的网格中放目标,子弹水平或垂直飞行,会打掉飞行路线上所有目标,问打掉所有目标,至少需要多少子弹。各从什么位置发射。
Konig定理:
二分图的最小顶点覆盖数等于最大匹配数。
证明:
为主便叙述,假设G分为左边X和右边Y两个互不相交的点集。。。。。。
假设G经过匈牙利算法后找到一个最大匹配M,则可知G中再也找不到一条增广路径。
标记右边未匹配边的顶点,并从右边未匹配边的顶点出发,按照边:未匹配->匹配->未匹配...,的原则标记途中经过的顶点,则最后一条经过的边必定为匹配边。重复上述过程,直到右边不再含有未匹配边的点。
记得到的左边已标记的点和右边未标记的点为S, 以下证明S即为所求的最小顶点集。
1。| S | == M
显然,左边标记的点全都为匹配边的顶点,右边未标记的点也为匹配边的顶点。因此,我们得到的点与匹配边一一对应。
2。S能覆盖G中所有的边。
上途S中点所得到的边有以下几种情况:
(1)左右均标记;
(2)左右均无标记;
(3)左边标记,右边未标记;
若存在一条边e不属于S所覆盖的边集,则e 左边未标记右边标记。
如果e不属于匹配边,那么左端点就可以通过这条边到达(从而得到标记);如果e属于匹配边,那么右端点不可能是一条路径的起点,于是它的标记只能是从这条边的左端点过来的左端点就应该有标记。
3。S是最小的覆盖。
因为要覆盖这M条匹配边至少就需要M个点。
/**
最小覆盖:选择尽量少的点,使得每条边至少有一个端点被选中
最小覆盖数等于最大匹配数。
*/
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
const int MAXV=1100; //v_t_l标记匈牙利树的X面的结点
bool G[MAXV][MAXV],vis[MAXV],vis_tree_l[MAXV],vis_tree_r[MAXV];
int match[MAXV],coving[MAXV]; //coving筛选出X面的未盖点
int R,C,N;
bool find_AP(int u){
for(int i=1;i<=C;i++){
if(!vis[i]&&G[u][i]){
vis[i]=true;
if(!match[i]||find_AP(match[i])){
match[i]=u;
return true;
}
}
}
return false;
}
void find_hungarytree(int u){
vis_tree_l[u]=1; //标记X面的结点
for(int i=1;i<=C;i++){
if(!vis[i]&&G[u][i]){
vis[i]=true;
vis_tree_r[i]=1; //标记Y面的结点
find_hungarytree(match[i]);//递归Yi对应的匹配点match[i]
}
}
}
int main(){
while(scanf("%d%d%d",&R,&C,&N)!=EOF&&(R+C+N)){
memset(G,0,sizeof(G));
memset(match,0,sizeof(match));
memset(vis_tree_l,0,sizeof(vis_tree_l));
memset(vis_tree_r,0,sizeof(vis_tree_r));
int a,b,ans=0;
int rc=R<C?C:R;
for(int i=1;i<=rc;i++){
coving[i]=i; //初始化未盖点数组
}
for(int i=0;i<N;i++){
scanf("%d%d",&a,&b);
G[a][b]=true;
}
for(int i=1;i<=R;i++){
memset(vis,0,sizeof(vis));
if(find_AP(i))
ans++;
}
printf("%d",ans);
for(int j=1;j<=C;j++){
if(match[j]) //如果是匹配点
coving[match[j]]=0; //表示为匹配点
}
for(int i=1;i<=R;i++){
if(!coving[i]) //过滤掉匹配点
continue;
memset(vis,0,sizeof(vis));
find_hungarytree(i);
} /*根据从X面未盖点扩展匈牙利树,X面中未标记点和Y面
中已标记点集合为最小覆盖,求出最小覆盖点集*/
for(int i=1;i<=R;i++){
if(vis_tree_l[i]==0){
printf(" r%d",i);
}
}
for(int i=1;i<=C;i++){
if(vis_tree_r[i]==1){
printf(" c%d",i);
}
}
printf("\n");
}
return 0;
}
/*
4 4 3
1 1
1 4
3 2
4 4 2
1 1
2 2
4 4 7
1 1
1 2
1 3
1 4
2 1
3 1
4 1
4 5 8
1 3
2 1
2 2
2 4
3 3
4 3
4 4
4 5
3 3 5
1 1
1 2
2 1
2 2
3 2
*/