我们来先了解一下什么是最小顶点覆盖;
图G的顶点覆盖是一个顶点集合V,使得G中的每一条边都接触V中的至少一个顶点。我们称集合V覆盖了G的边。最小顶点覆盖是用最少的顶点来覆盖所有的边。顶点覆盖数是最小顶点覆盖的大小。
相应地,图G的边覆盖是一个边集合E,使得G中的每一个顶点都接触E中的至少一条边。
如果只说覆盖,则通常是指顶点覆盖,而不是边覆盖。
在二分图中 :最大匹配数=最小顶点覆盖数;求二分图最大匹配可以用最大流(Maximal Flow)或者匈牙利算法(Hungarian Algorithm)
题意:鲍勃喜欢玩电脑游戏,特别是战略游戏,但有时他无法找到解决方案,速度不够快,那么他很伤心。现在,他有以下的问题。他必须捍卫一个中世纪的城市,形成了树的道路。他需要把最少数量的战士放在节点上,使他们可以观察所有的边(每个战士可以观察多条边)。你能帮助他吗?士兵,鲍勃把一个给定的树,你的程序应该计算最小的战士数量。输入文件包含多个数据集的文本格式。
分析:
首先来说第一种解法,即最小顶点覆盖问题。由König定理定理可知,二分图的最小顶点覆盖数等于二分图的最大匹配数。
关于König定理的证明网上也比较多。大家可以百度找一找。题目中的这棵树之所以可以当成二分图,是因为如果从一个点出发,那么可以将整棵树分成奇数点层和偶数点层。由于树是一种特殊的图。n个点由(n-1)条边连接起来。这样假定一个点为树的根,假设各点间的边权值为1。那么从树根出发遍历整棵树,根据各点到根的路径的奇偶性即可将所有点分成两个集合。奇数点与偶数点交替出现。假设奇数点与偶数点连边,偶数点则继续和下一层的奇数点连边。这就与二分图中同类集合点间无边,不同类集合点间有边相连吻合起来了。所以满足二分图的性质。也可以用二分图最大匹配进行求解。这个对点分成奇数点偶数点的方法与搜索剪枝中的奇偶剪枝很像。奇偶剪枝中对点的分类与该方法相同。
下面继续说二分图最大匹配。由于对该二分图进行了补全(无向图),边增加为原来边的二倍。所以最终结果要除以2。
二分图最小顶点覆盖=双向二分图最大匹配/2
#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
using namespace std;
const int maxn = 1505;
bool vis[maxn];//标记节点是否被访问过
int Left[maxn];//标记n个节点的增广节点的编号
vector<int> mp[maxn];
bool match(int cur)
{
for(int i = 0; i < mp[cur].size(); i++)
{
int v = mp[cur][i];
if(!vis[v])//若v与cur相邻,且没有被标记
{
vis[v] = 1;
if(Left[v] == -1 || match(Left[v]))//如果v未在前一个匹配M中,或者,v在匹配M中,但从v相邻的节点出发可以找到增广路
{
Left[v] = cur;//则把cur放到匹配M中
return true;
}
}
}
return false;
}
int main()
{
int n, u, v, num;
while(scanf("%d", &n) != EOF)
{
for(int i = 0; i < n; i++)
mp[i].clear();
for(int i = 0; i < n; i++)
{
scanf("%d:(%d)", &u, &num);
while(num--)
{
scanf("%d", &v);
mp[u].push_back(v);
mp[v].push_back(u);
}
}
int ans = 0;
memset(Left, -1, sizeof(Left));
for(int i = 0; i < n; i++)
{
memset(vis, false, sizeof(vis));
if(match(i))
ans++;//若有增广路,匹配数则加一
}
printf("%d\n", ans / 2);//最小顶点覆盖 == 最大匹配(双向图)/2;
}
return 0;
}