Luogu-P2015-二叉苹果树

此篇是本人的一次大胆尝试:一边做题一边写题解!

题目 P2015 二叉苹果树

题目描述

有一棵苹果树,如果树枝有分叉,一定是分二叉(就是说没有只有一个儿子的结点)

这棵树共有 NN 个结点(叶子点或者树枝分叉点),编号为 1N1 \sim N,树根编号一定是 11

我们用一根树枝两端连接的结点的编号来描述一根树枝的位置。下面是一颗有 44 个树枝的树:

1
2
3
4
5
2   5
\ /
3 4
\ /
1

现在这颗树枝条太多了,需要剪枝。但是一些树枝上长有苹果。

给定需要保留的树枝数量,求出最多能留住多少苹果。

输入格式

第一行 22 个整数 NNQQ,分别表示表示树的结点数,和要保留的树枝数量。

接下来 N1N-1 行,每行 33 个整数,描述一根树枝的信息:前 22 个数是它连接的结点的编号,第 33 个数是这根树枝上苹果的数量。

输出格式

一个数,最多能留住的苹果的数量。

思路:树形dp

既然是跟树相关,那肯定是树形dp啦!

边点转化

不难发现,一条边被删除和这条边对应的子节点被删除是等价的。

所以不妨把边的苹果数量转化为子节点上的苹果数量。

基本定义

dp[i][j]dp[i][j] 表示 ii 节点上保留 jj 个节点的最大苹果数。

son[i][0/1]son[i][0/1] 表示 ii 的左右儿子。

apple[i]apple[i] 表示节点 ii 上的苹果数量。

sonnode[i]son\\_node[i] 表示节点 ii 的子节点数量(含自身)。

建树

本题的建树较为特殊。因为在输入一条边时,无法确定哪个点是父节点~~(绝对不是我卡在这很长时间)~~。

所以先用邻接矩阵存储每一条边,之后从根节点 11 出发,获取每条边对应的另一个节点即为子节点。

使用 vis[i]vis[i] 记录节点 ii 是否被访问过。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
struct node{
int to, apple;
};

vector<node> arr[N];

void init(int f) {
if(vis[f]){
for(node n : arr[f]){
if(vis[n.to]) continue;
son[f][(bool)(son[f][0])] = n.to;
apple[n.to] = n.apple;
vis[n.to] = true;
init(n.to);
}
}
son_node[f] = son_node[son[f][0]] + son_node[son[f][1]] + 1;
}

int main(){
// ...
vis[1] = true;
init(1);
// ...
}

状态

  • 初状态:每个最深子节点上保留/舍弃自身的状态已知。

    想获取最深子节点,每次搜索到最深处不太合理,所以使用队列,从子节点一层层深入,同时记录当前节点。

    1
    2
    3
    4
    5
    6
    7
    queue<int> q;

    void init(int f){
    // ...
    son_node[f] = son_node[son[f][0]] + son_node[son[f][1]] + 1;
    q.push(f);
    }

    以上可看作树的后序遍历。

    如果 sonnode[x]son\\_node[x]11,即只有自己这个节点,那么 xx 就是最深层的子节点。

    1
    2
    3
    4
    5
    if(son_node[x] == 1) {
    dp[x][0] = 0;
    dp[x][1] = apple[x];
    continue;
    }
  • 状态转移方程:

    首先,一个节点 xx 有左子树和右子树。

    如果左子树保留 ii 个节点,右子树保留 jj 个节点,那么 xx 就含有 i+j+1i+j+1 个节点(含自身)。

    所以,状态转移方程如下。

    1
    dp[x][i + j + 1] = max(dp[x][i + j + 1], dp[son[x][0]][i] + dp[son[x][1]][j] + apple[x]);

    注:今后不再使用minset/maxset

  • 末状态:节点 11 上保留 QQ 个节点的苹果数量。

    1
    ans = dp[1][Q + 1];

AC Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
// template v8
#include <bits/stdc++.h>

using namespace std;
using ll = long long;

const int inf = 0x7f7f7f7f;

const int N = 110; // Q

int dp[N][N];

int son[N][2];
int son_node[N];

int apple[N];
bool vis[N];

int n, Q;

queue<int> q;

struct node{
int to, apple;
};

vector<node> arr[N];

void init(int f) {
if(vis[f]){
for(node n : arr[f]){
if(vis[n.to]) continue;
son[f][(bool)(son[f][0])] = n.to;
apple[n.to] = n.apple;
vis[n.to] = true;
init(n.to);
}
}
son_node[f] = son_node[son[f][0]] + son_node[son[f][1]] + 1;
q.push(f);
}

int main() {
// freopen("test.in", "r", stdin);
cin >> n >> Q;
int t1, t2, t3;
for(int i = 0; i < n - 1; i++) {
cin >> t1 >> t2 >> t3;
arr[t1].push_back({t2, t3});
arr[t2].push_back({t1, t3});
}
vis[1] = true;
init(1);
while(q.size()) {
int x = q.front();
q.pop();
if(son_node[x] == 1) {
dp[x][0] = 0;
dp[x][1] = apple[x];
continue;
}
for(int i = 0; i <= son_node[son[x][0]]; i++) {
for(int j = 0; j <= son_node[son[x][1]]; j++) {
dp[x][i + j + 1] = max(dp[x][i + j + 1], dp[son[x][0]][i] + dp[son[x][1]][j] + apple[x]);
}
}
}
cout << dp[1][Q + 1] << endl;
return 0;
}