2 5
\ /
3 4
\ /
1
|
| input | output |
|---|---|
5 2 1 3 1 1 4 10 2 3 20 3 5 20 |
21 |
刚开始学树状dp,欢迎来喷。
有一颗苹果树,它的树枝符合完全二叉树。每个树枝上有一些苹果。节点标号为1~N(1为根节点)。现在因为树枝太多了,需要保留q根树枝。问最多能保留多少苹果。
题目给出的数据并没有按照跟节点到子节点的顺序给出,如何建树便一直困扰了我好久(战五渣-。-)。 最终借助结构体存放两个儿子的下标比较优美地建出来了。
接下来讲转移。显然我们不光要考虑节点,还要考虑以该节点为根的树选择了多少条边。
dp[t][k] : 已 t 为根的子树保留k条边(树枝)最多能保留多少条苹果。
由此我们可以发现如果在某个节点保留了k条边,有以下两种情况。
1:只选择一棵子树。那么dp[t][k] = max(dp[t.l][k-1] + w[t][t.l], dp[t.r][k-1] + w[t][t.r]) //意思是在该子树(左或右)选择k-1条边,并加上根节点到该子树的边。
2:选择两颗子树。那么就存在两条固定的边(根节点到其左右孩子的边),dp[t][k] = w[t][t.l] +w[t][t.r] + max(dp[t.l][j] + dp[t.r][k - j - 2]) /*(j : 0 ~ K-2)*/ 。
即左子树和右子树总共选择k-2条边。
由此,输出dp[1][q]就好。
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
struct Node {
int l, r;
}T[105];
int n, q;
int dp[105][105], map[105][105];
void buildTree(int t) {//建树
int flag = 0;
for (int i=0; i<=n; i++) {
if (map[t][i] && !T[i].l && !T[t].r) {//!T[i].l不为父节点
if (!T[t].l) T[t].l = i;
else T[t].r = i;
flag = 1;
}
}
if (flag) {
buildTree(T[t].l);
buildTree(T[t].r);
}
}
void Tdp (int s) {
if (T[s].l == 0) return ;
Tdp(T[s].l);
Tdp(T[s].r);
for (int i=1; i<=q; i++) {//转移
dp[s][i] = max(dp[T[s].l][i-1] + map[s][T[s].l], dp[T[s].r][i-1] + map[s][T[s].r]);
for (int j=0; j<i-1; j++) {
dp[s][i] = max(dp[s][i], dp[T[s].l][j] + dp[T[s].r][i-j-2] + map[s][T[s].l] + map[s][T[s].r]);
}
}
}
int main () {
cin >> n >> q ;
int a, b, c;
for (int i=1; i<n; i++) {
cin >> a >> b >> c;
map[a][b] = map[b][a] = c;
}
buildTree(1);
Tdp(1);
cout << dp[1][q] <<endl;
return 0;
}原文地址:http://blog.csdn.net/xuelanghu407/article/details/43537855