Description:
太平王世子事件后,陆小凤成了皇上特聘的御前一品侍卫。
皇宫以午门为起点,直到后宫嫔妃们的寝宫,呈一棵树的形状;某些宫殿间可以互相望见。大内保卫森严,三步一岗,五步一哨,每个宫殿都要有人全天候看守,在不同的宫殿安排看守所需的费用不同。
可是陆小凤手上的经费不足,无论如何也没法在每个宫殿都安置留守侍卫。
Input:
帮助陆小凤布置侍卫,在看守全部宫殿的前提下,使得花费的经费最少。
Output:
输入文件中数据表示一棵树,描述如下:
第1行 n,表示树中结点的数目。
第2行至第n+1行,每行描述每个宫殿结点信息,依次为:该宫殿结点标号i(0<i<=n),在该宫殿安置侍卫所需的经费k,该边的儿子数m,接下来m个数,分别是这个节点的m个儿子的标号r1,r2,...,rm。
对于一个n(0 < n<=1500)个结点的树,结点标号在1到n之间,且标号不重复。
这道题中的数据是一棵树,很容易想到树形DP。//树的最小带权覆盖集
乍一看此题和战略游戏十分接近(所以我一开始就按战略游戏的代码写),但是往深处想他们之间还是有不同的。这道题要求最小花费,而战略游戏求解最少安放士兵的数目,所以在转移上有一点(很大)不同。
solution:
此题没有明确的说明根节点,所以在读入时要做一下处理,找到没有子结点的点当成根结点(下文中用root代替),方便进行树上递归。
转移:
前面也说过,树上的DP转移一般有两个方向:从根到叶和从叶到根。此题满足从叶到根。
对于每一个结点,只要在其本身或者父结点或者子结点有侍卫时,就一定可以被观察到,所以转移的状态可以简单的分为这三种。(son[x]表示x的子结点)
f[x][0]表示x没有侍卫,但x的父结点有侍卫;
f[x][1]表示x没有侍卫,但x的子结点有侍卫;
f[x][2]表示x本身有侍卫。
对于f[x][0]的情况:
此时x结点已经被观察到,因为想要保证花费最小,所以此时x结点不必要安放侍卫即可。
那么对于x的所有子结点只有两种情况:自己有侍卫或者可以被后代有侍卫。
状态转移方程是f[x][0]+=min(f[son[x]][2],f[son][x]][1])
(先不要纠结d是什么意思,等下我会解释)
对于f[x][1]的情况:
此时x结点已经被观察到,所以x结点不必安放侍卫。
只用找出x的所有子结点中花费最小的即可。
同样的,子结点也只有上述的两种情况。
状态转移方程是f[x][1]+=min(f[son[x]][2],f[son[x]][1])+d
这个充满神秘感的d又一次出现了,现在我要揭开它神秘的面纱:
d=min(d,f[son[x]][2]-min(f[son[x]][2],f[son[x]][1]))
因为刚才所说的都是对结点x进行决策的,对于f[x][0]和f[x][1],如果最优解是从f[son][x]][1]转移来的,那么必然也要加上它的花费。
所以d的作用就是判断是否是从f[son][x]][1]转移来,如果是就加上它的花费。(具体使用参考以下程序)
对于f[x][2]的情况:
此时x结点已经有侍卫了,x的儿子可以有侍卫,也可以被其子结点看守,还可以被其父结点看守。
所以状态转移方程是min(f[son[x]][0],f[son[x]][1],f[son[x]][2])+w[x]
(w[x]表示在x结点安放侍卫的花费)
出口:
最后对f[root][1]和f[root][2]取最小就是最终答案
具体转移就是这样,下面放代码咯:)
#include<bits/stdc++.h> using namespace std; vector<int> s[1505]; int w[1505]; int f[1505][3]; bool v[1505]; void dp(int u){ // f[u][2]//self // f[u][1]//son // f[u][0]//fa int y; int d=0x7fffffff/2; int sz=s[u].size(); for(int i=0;i<sz;i++){ dp(s[u][i]); f[u][0]+=min(f[s[u][i]][2],f[s[u][i]][1]); f[u][1]+=min(f[s[u][i]][2],f[s[u][i]][1]); d=min(d,f[s[u][i]][2]-min(f[s[u][i]][2],f[s[u][i]][1])); f[u][2]+=min(f[s[u][i]][2],min(f[s[u][i]][1],f[s[u][i]][0])); } f[u][1]+=d; f[u][2]+=w[u]; } int main(){ int n; cin>>n; int num,k,r,m; for(int i=1;i<=n;i++){ cin>>num>>k>>m; w[num]=k; for(int j=1;j<=m;j++){ cin>>r; v[r]=1; s[num].push_back(r); } } int root; for(int i=1;i<=n;i++){ if(v[i]==0){ root=i; break; } } dp(root); cout<<min(f[root][1],f[root][2]); return 0; }
代码中我用的是vector来实现链表,当然也可以用结构体来实现。
就这么多啦:)欢迎指正!!