前言:按照计划,昨天应该是完成树形DP7题和二分图、最大流基础专题,但是由于我智商实在拙计,一直在理解树形DP的思想,所以第二个专题只能顺延到今天了。但是昨天把树形DP弄了个5成懂我是很高兴的!下面我把这7题的解题思想和部分代码分享给大家。

题目一:皇宫看守
问题描述:
太平王世子事件后,陆小凤成了皇上特聘的御前一品侍卫。
皇宫以午门为起点,直到后宫嫔妃们的寝宫,呈一棵树的形状;某些宫殿间可以互相望见。大内保卫森严,三步一岗,五步一哨,每个宫殿都要有人全天候看守,在不同的宫殿安排看守所需的费用不同。
可是陆小凤手上的经费不足,无论如何也没法在每个宫殿都安置留守侍卫。

编程任务:
帮助陆小凤布置侍卫,在看守全部宫殿的前提下,使得花费的经费最少。

数据输入:
输入文件中数据表示一棵树,描述如下:
第1行 n,表示树中结点的数目。
第2行至第n+1行,每行描述每个宫殿结点信息,依次为:该宫殿结点标号i(0<i<=n),在该宫殿安置侍卫所需的经费k,该边的儿子数m,接下来m个数,分别是这个节点的m个儿子的标号r1,r2,…,rm。
对于一个n(0 < n<=1500)个结点的树,结点标号在1到n之间,且标号不重复。

数据输出:
输出文件仅包含一个数,为所求的最少的经费。

样例输入:
6
1 30 3 2 3 4
2 16 2 5 6
3 5 0
4 4 0
5 11 0
6 5 0

样例输出:
25

分析:本来这套题里还有一个题目叫“战略游戏”,抽象后的模型跟这题是一样的,遇到那题的时候我没太理解题解是怎么用树形DP做的,于是我就跳过了。后来遇到这题发现不研究的话不行。题目说的很清楚,用最少的点覆盖所有的点,(二分图里有一个模型叫用最少的点覆盖所有的边,以后再议)。如果是一个图的话,它是个NP完全问题,但题目给出的是个树,避免了后效性的问题,所以可以用动态规划来解决。
给出如下定义

    F[i,0]表示i点不放,且以i为根节点的子树(包括i节点)全部被观察到;
    F[i,1]表示i点不放,且以i为根节点的子树(可以不包括i节点)全部被观察到;
    F[i,2]表示i点放,且以i为根节点的子树全部被观察到;

转移如下
1、由F[i,0]定义可知,设j为i的儿子节点,至少要有一个i的儿子节点是放置守卫的,其余的儿子节点可放可不放,但由于根节点i不放,所以其余的儿子节点如果不放的话,必须保证能被观察到,即F[j][0];所以我们需要枚举必须放置的儿子节点,下面的转移方程描述的很清楚:

    F[i,0] = min{Sigma(min(F[j][0],F[j,2]))+F[k,2]},其中k为枚举的必放的儿子节点,j为除了k之外的儿子节点

2、由F[i,1]定义可知,i可以被观察到也可以不被观察到,但儿子节点必须都要被观察到,转移如下:

    F[i,1] = Sigma(min(F[j,0],F[j,2])) j是i的儿子节点

3、由F[i,2]定义可知,i点放置了守卫,所以对于每个儿子节点都能被观察到,取F[j,0],F[j,1],F[j,2]最小值即可:

    F[i,2] = min(F[j,0],F[j,1],F[j,2]) j是i的儿子节点
    对于叶节点i,F[i,0] = F[i,2] = data[i],F[i,1] = 0;

看了题解我是恍然大悟啊,智商不够,这个转移方程还是比较难想的。

参考代码

 1 //
 2 //  皇宫看守.cpp
 3 //  树形DP
 4 //
 5 //  Created by TimmyXu on 13-8-3.
 6 //  Copyright (c) 2013年 TimmyXu. All rights reserved.
 7 //
 8  
 9 #include <iostream>
10 #include <algorithm>
11 #include <cstdio>
12 #include <cstring>
13 #include <string>
14  
15 using namespace std;
16  
17 const int maxn = 1500+10;
18  
19 int f[maxn][3],data[maxn],n,son[maxn][maxn],len[maxn],du[maxn],x,root;
20  
21 void doit(int x)
22 {
23     if (len[x] == 0)
24     {
25         f[x][0] = f[x][2] = data[x];
26         f[x][1] = 0;
27         return;
28     }
29     for (int i = 1;i <= len[x];i++)
30         doit(son[x][i]);
31     f[x][0] = INT_MAX;
32     for (int i = 1;i <= len[x];i++)
33     {
34         int tmp = 0;
35         for (int j = 1;j <= len[x];j++)
36             if (i!=j)
37                 tmp += min(f[son[x][j]][0],f[son[x][j]][2]);
38         f[x][0] = min(f[x][0],tmp+f[son[x][i]][2]);
39     }
40     f[x][1] = 0;
41     for (int i = 1;i <= len[x];i++)
42         f[x][1] += min(f[son[x][i]][0],f[son[x][i]][2]);
43     f[x][2] = data[x];
44     for (int i = 1;i <= len[x];i++)
45         f[x][2] += min(f[son[x][i]][0],min(f[son[x][i]][1],f[son[x][i]][2]));
46 }
47  
48 int main()
49 {
50     scanf("%d",&n);
51     memset(data,0,sizeof(data));             //存每个点放置守卫的代价
52     memset(f,0,sizeof(f));                   //dp数组,F[i,j]含义如上述分析
53     memset(du,0,sizeof(du));                 //存储每个点的入度,用于找出根节点
54     memset(son,0,sizeof(son));               //存储每个点的儿子节点
55     memset(len,0,sizeof(len));               //存储每个点儿子节点个数
56     for (int i = 1;i <= n;i++)
57     {
58         scanf("%d",&x);
59         scanf("%d%d",&data[x],&len[x]);
60         for (int j = 1;j <= len[x];j++)
61         {
62             scanf("%d",&son[x][j]);
63             du[son[x][j]]++;
64         }
65     }
66     for (int i = 1;i <= n;i++)
67         if (du[i] == 0)
68         {
69             root =i;
70             break;
71         }
72     doit(root);
73     printf("%d\n",min(f[root][0],f[root][2]));
74 }
View Code

相关文章: