Problem Address:http://poj.org/problem?id=1947
【前言】
何谓“树形DP”?简单地说树形DP就是在树上进行的DP。
按我的理解,”树形DP“应该叫做“树维DP”。这么说,最简单的DP是一维DP,在一个数组上进行。接下去是二维DP,存放在一个二维数组里,其中一维用于表示所有的点,另一维用于表示该点的各种状态。而树形DP更多地像是二维DP,只不过表示所有点的那一维长成一棵树的形状。
一般地,二维DP用两个循环即可得出答案,而树形DP也是如此。不过,由于树的特殊结构,很多时候并不是直接就可以扫描。在树形DP上用得较多的或许就是所谓的“记忆化搜索”了。
于是,照葫芦画瓢地完成了第一道树形DP。
【思路】
建树:
这道题有多种做法,其中一种是把这棵树建成二叉树,即把普通的树转化为左儿子有兄弟的形式。
转移:
dp[i][j],表示对于结点i,使其得到结点数为j要去掉的最小边数。每个结点都有两种状态,选与不选。如果不选,则其值为其兄弟结点的值加一(去掉该点与父结点的连边)。如果选,则为其儿子结点与兄弟结点的值的和。
把每个结点作为根结点并枚举。除了真正意义上的根结点,其他结点枚举的结果都要加上一,表示把其跟其父节点的连边去掉所花费的代价。
【代码】
#include <iostream> using namespace std; const int maxn = 150; const int MAX = 99999999; int brother[maxn+5]; int son[maxn+5]; int dp[maxn+5][maxn+5]; void init(int n) { int i, j; for (i=0; i<=n; i++) { brother[i] = son[i] = 0; for (j=0; j<=n; j++) { dp[i][j] = MAX; } } } int solve(int v, int j) { int ans, t1, t2, k; if (dp[v][j]<MAX) return dp[v][j]; if (v==0 && j==0) return dp[0][0] = 0; if (v==0 && j!=0) return dp[0][j] = -1; ans = solve(brother[v], j); if (ans!=-1) ans++;//不选该结点的值 else ans = MAX; for (k=0; k<=j-1; k++) { t1 = solve(son[v], k); t2 = solve(brother[v], j-k-1); if (t1>-1 && t2>-1 && t1+t2<ans) ans = t1 + t2;//选该结点的值 } if (ans==MAX) ans = -1; return dp[v][j] = ans; } int main() { int n, p; int a, b, ans; int i, t; scanf("%d %d", &n, &p); init(n); for (i=1; i<n; i++) { scanf("%d %d", &a, &b); brother[b] = son[a]; son[a] = b; } ans = solve(son[1], p-1);//单独处理根结点 for (i=1; i<=n; i++) { t = solve(son[i], p-1); if (t>-1 && t+1<ans) ans = t+1;//枚举各结点的结果加一 } printf("%d\n", ans); return 0; }