【题目描述】

原题目为英文,摘要题意如下:

给定一颗N(N ≤ 100)个节点的树,每个节点上长有一定数量的苹果。 现有一个女孩从1出发在树上行走,最多走k(k ≤ 200)步,会吃掉经过路径节点上的所有苹果。 求她最多能吃到多少苹果。

【题目链接】

POJ 2486 Apple Tree

【解题思路】

分析一下其实是一个树上分组背包的问题。 每个子节点相当于一组,对该子节点分配一定步数可以获得一定收益——可以看做组内物品。

我们很自然的想到用$f[i][j]$表示在$i$子树中走$j$步所能获得的最大收益。 但是我们随即发现状态信息不够,走完这$j$步是否回到$i$是对接下来的决策有影响的。 考虑加半维表示回不回来。

设$f[i][j]$表示在$i$子树中走$j$步 后不回到i 所能获得的最大收益。 设$g[i][j]$表示在$i$子树中走$j$步 后要回到i 所能获得的最大收益。

决策与状态转移还请参见代码吧,注释中给出了较详细的说明。

【AC代码】

#include <cstdio>
#include <algorithm>
#include <vector>
#include <queue>
#include <cstring>

#define MAXK 200
#define MAXN 100

inline void updateMax(int &x, int y){
    x = std::max(x, y);
}

int n, m;

struct Node{
    Node *child, *next;
    int w;
    int f[MAXK + 1], g[MAXK + 1];

    bool asked;
    std::vector<Node*> nodes;

    void solve(){
        f[0] = g[0] = w;

        for(Node *v = child; v; v = v->next){
            // 所有的组
            v->solve();

            for(int i = m; i >= 0; i--){
                // 一共i步。

                for(int k = 0; k < i; k++){
                    // 分配k步给当前子节点v,但是有不同的走法。

                    if(k + 2 <= i){
                        updateMax(g[i], g[i - k - 2] + v->g[k]);
                        // 一步走到v,在v子树中k步回到v,然后在一步走回到this,再在this子树中走i - k - 2步回来。
                        updateMax(f[i], f[i - k - 2] + v->g[k]);
                        // 一步走到v,在v子树中k步回到v,然后在一步走回到this,再在this子树中走i - k - 2步不回来。
                    }
                    if(k + 1 <= i){
                        updateMax(f[i], g[i - k - 1] + v->f[k]);
                        // 在this子树中走i - k - 1步回来,然后一步走到v,在v子树中走k步不回来。
                    }
                }
            }
        }
    }
    
} nodes[MAXN];

Node *root = nodes;

inline void addUEdge(int a, int b){
    Node *u = nodes + a, *v = nodes + b;
    u->nodes.push_back(v), v->nodes.push_back(u);
}

inline void addChild(Node *f, Node *c){
    c->next = f->child, f->child = c;
}

inline void cleanUp(){
    for(Node *v = nodes; v != nodes + n; v++){
        v->child = v->next = NULL;
        v->nodes.clear();
        v->asked = false;
        memset(v->f, 0, sizeof v->f);
        memset(v->g, 0, sizeof v->g);
    }
}

inline void convert(){
    std::queue<Node*> Q;

    Q.push(root);
    root->asked = true;

    while(!Q.empty()){
        Node *v = Q.front(); Q.pop();
        for(int i = 0; i < v->nodes.size(); i++){
            Node *vi = v->nodes[i];
            if(!vi->asked){
                addChild(v, vi);

                Q.push(vi);
                vi->asked = true;
            }
        }
    }
}

int main(){
    while(scanf("%d%d", &n, &m) == 2){
        cleanUp();

        for(Node *v = nodes; v != nodes + n; v++) scanf("%d", &v->w);
        for(int i = 0; i < n - 1; i++){
            int u, v;
            scanf("%d%d", &u, &v), u--, v--;
            addUEdge(u, v);
        }

        convert();
        root->solve();

        int ans = 0;
        for(int i = 0; i <= m; i++) updateMax(ans, root->f[i]);
        printf("%d\n", ans);
    }

    return 0;
}

就是这样啦。