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; }