题目:http://poj.org/problem?id=3041
题意:
n*n矩阵,k组数据,每组u,v表示,矩阵【u,v】有障碍物,每次子弹操作可以清楚一行或一列的障碍物,求最少操作数
分析:
把行列划分成两个点集,x集合(行)、y集合(列),把有向图G矩阵转化成两个点集的二部图,则【u,v】表示为x【u】 = v, y【v】 = u
当把x【1】去掉的时候,则表示点1能到的点都被去掉了。可以理解为,一个点覆盖其他点
所以,只要求用最少的点覆盖所有的点即可 == 最小点覆盖
有定理可得: 最小点覆盖 = 最大匹配->求最大匹配即可
(*)最小点覆盖:
每一条边上至少有一个点被选中,即一个点可以覆盖若干条边,选择最少的点,使得所有边都被覆盖,则点数为最小点覆盖数
代码:
#include <stdio.h>
#include <iostream>
#include <string.h>
#include <string>
#include <math.h>
#include <algorithm>
#include <queue>
#include <stack>
#include <vector>
#include <map>
using namespace std;
#define MAX 500+10
#define MIN -1e9
#define INF 0x7f7f7f7f
int t, n, m;
int x[MAX], y[MAX]; // 点集合
int Edge[MAX][MAX]; // 有向图
int vis[MAX]; // 递归函数中防止重复访问的数组
int path(int u) // 试图匹配,包含更新匹配
{
for(int v = 1; v<=n; v++) // 遍历y
{
if(vis[v] == 0 && Edge[u][v] == 1) // 当前DFS未访问过该y节点 && 能够相连
{
vis[v] = 1;
if(y[v] == -1 || path(y[v])) // 当前y未连接过x || 已连接,但是可以将其更新为其他节点,把该节点空出给当前y
{
x[u] = v;
y[v] = u;
return 1; // 匹配成功
}
}
}
return 0; // 匹配失败
}
void slove()
{
memset(x, -1, sizeof(x));
memset(y, -1, sizeof(y));
int sum = 0; // 成功匹配数
for(int u = 1; u<=n; u++)
{
if(x[u] == -1) // 未匹配过的行, 有机会更新匹配
{
memset(vis, 0, sizeof(vis)); // 每次查找路径前,y的访问清空(每个x均可访问所有y)
if(path(u) == 1) // 匹配成功
sum ++ ;
}
}
printf("%d\n", sum);
}
int main()
{
int i, j;
//freopen("a.txt", "r", stdin);
while(~scanf("%d%d", &n, &m))
{
int u, v;
memset(Edge, 0, sizeof(Edge));
for(i = 0; i<m; i++)
{
scanf("%d%d", &u, &v);
Edge[u][v] = 1;
}
slove();
}
return 0;
}