前言:按照计划,昨天应该是完成树形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 }