树形DP 或 最小顶点覆盖=最大匹配(双向图)(HDU 1053)

来源:互联网 发布:如何做淘宝客推广 编辑:程序博客网 时间:2024/05/13 05:16

Strategic Game

Time Limit: 20000/10000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 5170    Accepted Submission(s): 2377

Problem Description

Bob enjoys playing computer games, especially strategic games, but sometimes he cannot find the solution fast enough and then he is very sad. Now he has the following problem. He must defend a medieval city, the roads of which form a tree. He has to put the minimum number of soldiers on the nodes so that they can observe all the edges. Can you help him?

Your program should find the minimum number of soldiers that Bob has to put for a given tree.

The input file contains several data sets in text format. Each data set represents a tree with the following description:

the number of nodes
the description of each node in the following format
node_identifier:(number_of_roads) node_identifier1 node_identifier2 ... node_identifier
or
node_identifier:(0)

The node identifiers are integer numbers between 0 and n-1, for n nodes (0 < n <= 1500). Every edge appears only once in the input data.

For example for the tree: 



the solution is one soldier ( at the node 1).

The output should be printed on the standard output. For each given input data set, print one integer number in a single line that gives the result (the minimum number of soldiers). An example is given in the following table:

 

 

Sample Input

4

0:(1) 1

1:(2) 2 3

2:(0)

3:(0)

5

3:(3) 1 4 2

1:(1) 0

2:(0)

0:(0)

4:(0)

 

 

Sample Output

1

2



这道题有两种解题方法:

第一种方法是树形DP:

对于每一个节点,节点的yes表示该节点存在士兵,如果为no的话该节点则不存在士兵,用深搜然后进行回朔,如果某个节点的父节点存在士兵,则该节点可能有士兵或者没有士兵,如果某个节点的父节点不存在士兵的话,那么子节点一定存在士兵。

代码:

#include<stdio.h>#define M 1505struct node{int child[M];int len;int yes,no;};int n,root,a,b,c;node nd[M];int min(int a, int b){return a<b?a:b;}void dfs(int r){nd[r].yes = 1;nd[r].no = 0;for(int i=0;i<nd[r].len;i++){dfs(nd[r].child[i]);nd[r].yes += min(nd[nd[r].child[i]].yes, nd[nd[r].child[i]].no);nd[r].no += nd[nd[r].child[i]].yes;}}int main(){while(scanf("%d", &n)!=EOF){for(int i=0;i<n;i++){scanf("%d:(%d)", &a,&b);if(i==0)root = a;nd[a].len = b;for(int j=0;j<b;j++){scanf("%d",&c);nd[a].child[j] = c;}}dfs(root);printf("%d\n", min(nd[root].yes, nd[root].no));}return 0;}

第二种方法为最小顶点覆盖==最大匹配(双向图)

二分图中,选取最少的点数,使这些点和所有的边都有关联(把所有的边的覆盖),叫做最小点覆盖。

 

证明最大匹配==最小顶点覆盖

已知某个双向图是最大匹配,所以再也找不到增广路,假设所有的匹配边为n条,那么左右各有n个匹配的点。

标记所有左边匹配边的顶点,则有n个。问题就是证明n=最小点覆盖,即证明最大匹配数n到底能不能覆盖所有的边入手。

对于剩下的未匹配的边,每条边的右边点和左边点,假设左边点不属于上述所说的n个左边,那么就新增了一条增广路(想想增广路的定义就知道了,右未匹配,左未匹配的话那就可以找到增广路了)所以左边点也在匹配边之中,。所以就证明了剩下的未匹配边关联的范围也在这左边的n个匹配点的范围内力了。

代码:

#include<stdio.h>#include<string.h>#include<vector>#include<iostream>using namespace std;#define M 1505int n,a,b,c;vector<int> f[M];int used[M],link[M];int dfs(int a){for(int i=0;i<(int)f[a].size();i++){int j = f[a][i];if(used[j]==0){used[j] = 1;if(link[j]==-1 || dfs(link[j])){link[j] = a;return 1;}}}return 0;}int maxmatch(){int ans = 0;memset(link, -1, sizeof(link));for(int i=0;i<n;i++){memset(used, 0, sizeof(used));ans += dfs(i);}return ans;}int main(){while(scanf("%d", &n)!=EOF){for(int i=0;i<n;i++){f[i].clear();}for(int j=0;j<n;j++){scanf("%d:(%d)", &a, &b);for(int i=0;i<b;i++){scanf("%d", &c);f[a].push_back(c);f[c].push_back(a);}    }printf("%d\n", maxmatch()/2);}return 0;}



1 0