POJ-1947 Rebuilding Roads 树形DP

这题在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;    
}

 

posted @ 2013-01-17 15:21  沐阳  阅读(374)  评论(0编辑  收藏  举报