http://poj.org/problem?id=3041
题意:
在N*N(1 <= N <= 500)的平面上有K颗小行星,现在你要摧毁他们,你的每一发子弹可以摧毁同一行,或者是同一列上的小行星,现在问你最少要多少子弹才能摧毁所有的小行星?
分析:
构造一个二分图,用每行的行号做集合1中的点,用每行的列号做集合2中的点,如果第 i 行第 j 列有一个小行星,就可以把集合1中的 i 行点与集合2中的 j 号点连一条边。
构造好二分图之后,会发现,现在的问题变成了找出最小的一个点集,使所有的边至少有一个点在该点集内。
这就是典型的最小点集覆盖问题了,而于由二分图最小覆盖点集等于其最大匹配(一个经典的定理,证明在http://www.matrix67.com/blog/archives/116,其中还有怎样求具体的最小覆盖集中的点的方法 ),可知,只用求该二分图的最大匹配就行了。
代码:
#pragma warning (disable:4786)
#include<iostream>
using namespace std;
int match[505];
int map[505][505];
int visited[505];
int n;
//寻找起点为点集1中的node的增广轨
bool find( int node ){
int i;
/// 对点集2中的每个与node有边相连的点 i , 如果 i 还没有与之匹配的点,则将node 与 i 配对,
/// 如果 i 已与点 j 配对,则搜索有无其他的可与 j 配对的点 k,如果 k 存在,则将 j 与 k 配
/// 对,node与 i 配对,这就使原本的配对集 { i—j } 拓展到了 { node—i ,j—k },增加了一条匹配边
for( i = 1; i <= n; i ++ ){
if( !visited[i] && map[node][i] == 1 ){
visited[i] = 1;
if( match[i] == 0 || find( match[i] ) ){
match[i] = node;
return true;
}
}
}
return false;
}
int main(){
int i,k,sum = 0;
scanf("%d%d",&n,&k);
int x,y;
memset( match, 0, sizeof( match ) );
memset( map, 0, sizeof( map ) );
for( i = 0; i < k; i ++ ){
scanf("%d%d",&x,&y);;
map[x][y] = 1;
}
// 对点集1中的每个点寻找增广轨
for( i = 1; i <= n; i ++ ){
memset( visited, 0, sizeof( visited ) );
if( find(i) )
sum ++;
}
printf("%d\n", sum);
return 0;
}