这题在yefeng1627的淫威下迅速屈服. 由刚开始的一个较动态方程便很好的解决了组合问题, 再加之进一步分析, 将本来应该要的辅助状态删除, 剩下的就是一个非常优美的动态规划方程了.
详见代码:
#include <cstdlib> #include <cstdio> #include <cstring> #include <algorithm> #include <iostream> #define INF 0x3f3f3f3f using namespace std; /* 题意:给定一棵树, 现在要求从这棵树中分割出P个节点的子树, 问最少要破坏多少条路 解法:首先这个问题的复杂性主要在于如果这个分割出来的子树包含点A,那么A的子树各对这 P个节点贡献多少呢, 这是一个组合问题, 由于节点个数达到可能100以上, 因此组合 情况非常多, 这是状态如何选取就具有一定的技巧性. 既然组合情况很多, 那么我们 开设的状态也同样是一个非常模糊的状态, 即只值保留最优值, 而对其实如何而来的 并不在乎, 于是便有状态 dp[x][i][j]表示第x号节点处理到第i棵子树时分割出剩下j 个节点的子树所需要破坏的最少路径是多少. 分割出来的节点不为零就一定包含x,那 么就有动态方程: dp[x][i][j] = min(dp[x][i-1][k] + dp[xi][ch[xi]][j-k]); 这个方程意思就是处理到i个子树的时候分割出j个节点的方式是有前i-1棵子树和当前 这棵子树的组合, 这样就每次只考虑两个部分的组合情况, 而不必纠结多个子树时如何 组合的. 注意边界情况; dp[x][0][1] = 0, 表示还没处理子树时, 当前节点是存在的 dp[x][ch[x]][0] = 1, 表示包含x的这棵子树如果都不取的话,那么只需要切断上面的路 */ int N, P, idx, ch[155], head[155]; int ret, dp[155][155][155]; struct Node { int v, next; }e[155]; void insert(int a, int b) { ++idx; e[idx].v = b, e[idx].next = head[a]; head[a] = idx; } void dfs(int x) { if (head[x] == -1) { // 该节点是叶子节点 dp[x][0][0] = 1; dp[x][0][1] = 0; // WA一次因为该处直接return导致最后没有统计到这个结果 } else { dp[x][0][1] = 0; dp[x][ch[x]][0] = 1; for (int i = head[x], k = 1; i != -1; i = e[i].next, ++k) { // 枚举一个处理到第k棵子树 dfs(e[i].v); for (int j = 1; j <= P; ++j) { for (int m = 0; m < j; ++m) { // 由于根节点已经有1个节点, 所以只要孩子中有P-1个节点足矣 dp[x][k][j] = min(dp[x][k][j], dp[x][k-1][j-m]+dp[e[i].v][ch[e[i].v]][m]); } } } } if (dp[x][ch[x]][P] != INF) { if (x != 1) { ret = min(ret, dp[x][ch[x]][P]+1); } else { ret = min(ret, dp[x][ch[x]][P]); } } } int main() { int a, b; while (scanf("%d %d", &N, &P) == 2) { if (N == 1) { printf("0\n"); continue; } idx = -1; ret = INF; memset(dp, 0x3f, sizeof (int [155][155][155])); memset(ch, 0, sizeof (ch)); memset(head, 0xff, sizeof (head)); for (int i = 1; i < N; ++i) { scanf("%d %d", &a, &b); insert(a, b); ++ch[a]; // a节点的孩子数加1 } dfs(1); printf("%d\n", ret); } return 0; }