题目链接:https://www.luogu.com.cn/problem/P2015
CSDN食用链接:https://blog.csdn.net/qq_43906000/article/details/107952301
题目描述
有一棵苹果树,如果树枝有分叉,一定是分2叉(就是说没有只有1个儿子的结点)
这棵树共有N个结点(叶子点或者树枝分叉点),编号为1-N,树根编号一定是1。
我们用一根树枝两端连接的结点的编号来描述一根树枝的位置。下面是一颗有4个树枝的树
2 5
/
3 4
/
1
现在这颗树枝条太多了,需要剪枝。但是一些树枝上长有苹果。
给定需要保留的树枝数量,求出最多能留住多少苹果。
输入格式
第1行2个数,N和Q(1<=Q<= N,1<N<=100)。
N表示树的结点数,Q表示要保留的树枝数量。接下来N-1行描述树枝的信息。
每行3个整数,前两个是它连接的结点的编号。第3个数是这根树枝上苹果的数量。
每根树枝上的苹果不超过30000个。
输出格式
一个数,最多能留住的苹果的数量。
输入
5 2
1 3 1
1 4 10
2 3 20
3 5 20
输出
21
emmm,在刚学编程5分钟的萌新眼中看来,这题确实难道蒟蒻的我了QAQ,想了一个小时才想出来。。。。
实际上它已经挺明显地告诉你状态了,也就是第几个节点保留几根枝条,可得状态(dp[i][j])表示节点(i)保留(j)个枝条的最多苹果数。由于只有两个儿子,那么你可以直接枚举当前节点保留的枝条数(j),再枚举左儿子保留(k)个枝条,右儿子则保留(j-k)个,只不过需要注意的是当前节点的枝条数一定会比每个儿子的多一个枝条,所以我们需要对左儿子枚举到(k:0-size(lson)+1)(实际上也就是枚举左边的保留(k)个,右边保留多少个),而取的时候取左儿子的时候一定会带上牵连左儿子的那根枝条,所以它由左儿子转移过来的时候是由(dp[lson][k-1]+w1)转移过来的,其中(w1)表示的是与左儿子相连的枝条的苹果数。同理可得右儿子。那么代码也就不难码出来了。
以下是AC代码:
#include <bits/stdc++.h>
using namespace std;
#define mak make_pair
const int mac=1e3+10;
vector<pair<int,int> >g[mac];
int dp[200][200],n,q,sz[200];
void dfs(int u,int fa)
{
int v1=0,v2=0,w1,w2;
for (auto v:g[u]){
if (v.first==fa) continue;
dfs(v.first,u);
if (v1) v2=v.first,w2=v.second;
else v1=v.first,w1=v.second;
}
if (!v1) return;
for (int i=1; i<=min(q,sz[u]); i++){//枚举当前保留的枝条数
for (int j=0; j<=min(i,sz[v1]+1); j++){//枚举左边保留的枝条数
int p;
if (!j || !(i-j)) p=max(dp[v2][i-1]+w2,dp[v1][i-1]+w1);
else p=max(dp[v1][j-1]+dp[v2][i-j-1],dp[v2][j-1]+dp[v1][i-j-1])+w1+w2;
dp[u][i]=max(dp[u][i],p);
}
}
}
void dfs_size(int u,int fa)
{
for (auto v:g[u]){
if (v.first==fa) continue;
dfs_size(v.first,u);
sz[u]+=sz[v.first]+1;
}
}
int main(int argc, char const *argv[])
{
scanf ("%d%d",&n,&q);
for (int i=1; i<n; i++){
int u,v,w;
scanf ("%d%d%d",&u,&v,&w);
g[u].push_back(mak(v,w)); g[v].push_back(mak(u,w));
}
dfs_size(1,0);
dfs(1,0);
printf("%d
",dp[1][q]);
return 0;
}