这道题非常经典,而且解法也比较多。
首先来说第一种解法,即最小顶点覆盖问题。由König定理定理可知,二分图的最小顶点覆盖数等于二分图的最大匹配数。
关于König定理的证明网上也比较多。大家可以百度找一找。题目中的这棵树之所以可以当成二分图,是因为如果从一个点出发,那么可以将整棵树分成奇数点层和偶数点层。由于树是一种特殊的图。n个点由(n-1)条边连接起来。这样假定一个点为树的根,假设各点间的边权值为1。那么从树根出发遍历整棵树,根据各点到根的路径的奇偶性即可将所有点分成两个集合。奇数点与偶数点交替出现。假设奇数点与偶数点连边,偶数点则继续和下一层的奇数点连边。这就与二分图中同类集合点间无边,不同类集合点间有边相连吻合起来了。所以满足二分图的性质。也可以用二分图最大匹配进行求解。这个对点分成奇数点偶数点的方法与搜索剪枝中的奇偶剪枝很像。奇偶剪枝中对点的分类与该方法相同。
关于奇偶剪枝,见http://blog.csdn.net/eclipse88/article/details/6475127
奇偶剪枝经典例题:HDU1010
下面继续说二分图最大匹配。由于对该二分图进行了补全(无向图),边增加为原来边的二倍。所以最终结果要除以2。
二分图最小顶点覆盖=双向二分图最大匹配/2
二分图最大匹配代码:
#include
#include
#include
#include
#include
#define CLR(x) memset(x,0,sizeof(x))
#define __CLR(x) memset(x,-1,sizeof(x))
using namespace std;
vectorG[1510];
bool vis[1510];
int match[1510];
bool dfs(int u)
{
for(int i=0;i
方法二是树形DP。本题也是树形DP的经典例题之一。
按照同样的方法,我们也可以对整个树进行分层,然后一层一层遍历。当然在这之前就得把无根树变成有根树。由于树本来是连通的,所以从任意一点出发均可以访问到整棵树的所有节点。首先我们找出一个点作为根,然后进行dfs遍历整棵树。进行dp的时候对于一个结点有两种决策,选择这个结点或者不选。
如果不选择该节点作为覆盖点,那么必定要选择它的全部子节点作为覆盖点来覆盖掉与它相连的边。
如果选择该节点作为覆盖点,那么就要考虑是否选择它的子节点,我们取优,因此选取子节点在两种决策下的最小值。
还有要说明的是由于树的天然优良性,边为(n-1)条,很少。所以直接存储边是很明智的。存储边时采用还是邻接表存储。插入边时选取头插法。
树形dp代码:
#include
#include
#include
#include
#include
#define CLR(x) memset(x,0,sizeof(x))
#define __CLR(x) memset(x,-1,sizeof(x))
using namespace std;
struct edge
{
int to,next;
}e[1505];
int h[1505],dp[1505][2],num=1;
void addedge(int u,int v)
{
e[num].to=v;
e[num].next=h[u];
h[u]=num++;
}
void dfs(int u)
{
int v,i;
dp[u][0]=0;
dp[u][1]=1;
for(i=h[u];i!=-1;i=e[i].next)
{
v=e[i].to;
dfs(v);
dp[u][0]+=dp[v][1];
dp[u][1]+=min(dp[v][0],dp[v][1]);
}
}
int main()
{
int n;
while(~scanf("%d",&n))
{
__CLR(h);
int rt=-1;
num=1;
for(int i=1; i<=n; i++)
{
int u,k;
scanf("%d:(%d)",&u,&k);
for(int j=1; j<=k; j++)
{
int v;
scanf("%d",&v);
addedge(u,v);
}
if(rt==-1) rt=u;
}
dfs(rt);
int res=min(dp[rt][0],dp[rt][1]);
printf("%d\n",res);
}
}
方法三就是贪心。贪心的策略是选择点的度数越多的点价值越高。度数为1的点价值最小。当然我们可以找出与度数为1的点相连的那些点作为覆盖点。并删除与这些点相连的边。这个方法与拓扑排序的思想有很大的相似性。从结点的度上入手考虑,并记录每个结点的度数,在进行完决策后进行删边操作,直到最终没有点的度为1为止。进行操作时可以用队列实现。感觉与拓扑排序真的好像。虽然目的不同,但思想相通。
用visit数组记录结点是否被访问过,保证每个结点被访问一次。(边是无向边)
贪心代码:
#include
#include
#include
#include
#include
#include
#define CLR(x) memset(x,0,sizeof(x))
#define __CLR(x) memset(x,-1,sizeof(x))
#define pb push_back
using namespace std;
vectorG[1505];
int indx[1505],vis[1505],n;
void solve()
{
CLR(vis);
queue q;
for(int i=0; i
这道题一题多解,涉及的知识面很广,不得不钻研一下。