您的位置:首页 > 移动开发

Timus Online Judge 1018 Binary Apple Tree(树型dp)

2015-10-27 11:37 429 查看
题目大意:一棵有N个节点苹果树,有N - 1个分支,每个分支上面会有一定数量的苹果。问,在保留Q个分支的情况下,保留的最多的苹果的数量是多少?(当然,要让余下的Q个分支都连通才行。)

简单的思路转换一下,我们把苹果放在节点上面,让根节点1的苹果的数量为0。然后,比如说2 5 8表示节点2和5两个节点之间的分支上有8个苹果,我们就把8这个值放在2 和 5中的子节点上。我不懂是不是必须这样建树,但是感觉这样建树会比较正常。

如果你完全没有dp的概念的话,你可能会陷入一个误区,就是每次都删除点这棵树上的最小叶子节点,直到剩下Q + 1 个节点的时候(因为要剩下Q个分支,就必须要有Q + 1个节点)。如果你真的是这样思考的,那么来看看这一组数据N = 5, Q = 2, edge(1, 2) = 100, edge(1, 3) = 10, edge(2, 4) = 11, edge(3, 5) = 99。每次你选最小的分支删除,最后的答案的110,然后正确答案是111。所以我们必须要综合考虑整个局面。

我dp弱爆了,所以一般遇到dp一类的问题,我都会优先选择记忆化搜索。其实道理都是差不多的,我就是不会去推状态转移方程。

忘了说一点,解这道题目的时候我们还需要一次思维转换,就是将问题中剩余Q个分支的最多的苹果数量转换为选苹果数量最少的N - Q - 1 个节点删除掉。

那么我们就可以这样去定义状态,Remove(id, k) = (根节点的编号为id的子树,删除掉k个节点(必须保证连通性),这k个节点上苹果数量的最小值),那么,我们自顶向下的去求解一遍,用记忆化数组dp来保存一下中间结果,避免子问题重复求解,那么这道题目就轻松解决了。

下面又是自己那丑陋的代码。这道题目wrong了四次,原因是读题不仔细,以为是求删除掉Q个分支后的最大苹果数量,wrong哭了之后一直找不到原因,又读了3遍题目才发现求的是剩余Q个分支,真的是够了。

#include <iostream>
#include <vector>
#include <cstring>
#define pii pair<int, int>
#define x first
#define y second
using namespace std;
class Node{
public:
int id, val, size, sum;//其实感觉有些变量定义来用处不大,自己思考过后随意吧
  Node *l, *r;
Node(int id, int val) {
this->id = id;
this->val = val;
this->size = 1;
this->sum = val;
l = r = NULL;
}
};
int much[101][101], dp[101][101], N, Q;
void Build(Node *head) {
Node *L, *R;
L = R = NULL;
for (int i = 1; i <= N; ++i) {//建树的时候注意是双向的,因为题目中并没有说明哪个是父节点,哪个是子节点
if (much[head->id][i] == -1) continue;
if (L == NULL) {
L = new Node(i, much[head->id][i]);
much[head->id][i] = much[i][head->id] = -1;
Build(L);
head->size += L->size;
head->sum += L->sum;
}
else {
R = new Node(i, much[head->id][i]);
much[head->id][i] = much[i][head->id] = -1;
Build(R);
head->size += R->size;
head->sum += R->sum;
}
}
head->l = L, head->r = R;
}
int Remove(Node *head, int k) {//其实写过线段树的朋友会发现,这段代码似曾相识,思考一下问题原型就懂了
if (dp[head->id][k] != -1) return dp[head->id][k];
if (k == 0) return dp[head->id][k] = 0;//记录的是删去k个节点的最小的苹果的数量,删去k个节点,那么删去的苹果数量当然是0咯
if (k == head->size) return dp[head->id][k] = head->sum;
if (head->l == NULL) return dp[head->id][k] = Remove(head->r, k);
if (head->r == NULL) return dp[head->id][k] = Remove(head->l, k);
int ans = 30000000;
for (int i = 0; i <= k; ++i) {
if (head->l->size < i || head->r->size < k - i) continue;//如果这棵子树上的节点个数小于要删去的节点个数,那么这种情况肯定是不允许的
ans = min(ans, Remove(head->l, i) + Remove(head->r, k - i));
}
return dp[head->id][k] = ans;
}
int main() {
cin >> N >> Q;
memset(much, -1, sizeof much);
memset(dp, -1, sizeof dp);
int sum = 0;//用总数sum 减去 删掉的N - Q - 1个节点最少的苹果的数量 == 剩余的Q个分支上的最多的苹果数量
for (int i = 1; i < N; ++i) {
int a, b, c;
cin >> a >> b >> c;
much[a][b] = much[b][a] = c;
sum += c;
}
Node *head = new Node(1, 0);
Build(head);
cout << sum - Remove(head, N - Q - 1) << endl;//之前wrong了四把都是传的Q,真的是哭晕。
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  dp