原题链接:https://www.luogu.org/problem/P2015
题意就是给你n-1条树枝,每条树枝上都带有一定的苹果,问最后留下m根树枝,能拥有的苹果的最大值,根节点为1,每个节点只有2种情况,有2个儿子,或没有儿子。
这题是一个很典型的树形结构,因为节点以边相连,又为二叉结构,无环。我们这时候从根节点下顺,找到最深的儿子,然后一步步开始回溯,以当前回溯到的这个点作为根,考虑他
有几条边的最大值。那从下至上递推回去,根节点的状态只与2个子节点有关,很明显,这就是一个dp,且是树形的。
本题的状态转移方程要先列出:dp[i][j]=max{ dp[i][j],dp[i][j-k-1]+dp[son[i]][k]+val[i][son[i]] }(0<=j<=m,0<=k<j).接下来解释这个方程从何而来。
以样例为例,我们这时候从根节点1向下搜索,找到了2,可是2这时候作为单点,并不存在边,便回溯,到3,这时候3-2有一条边,我们可以考虑是否要添这条边,dp[3][1]这时候要由dp[2][0]
转移而来,为什么呢,因为这时候3如果要与2相连,是肯定要加3-2这条边,不然子树与根就无法连接起来了吧。接下来查到5这个点回溯,可以处理dp[3][2]了,dp[3][2]只由dp[5][0]和dp[5][1]而来,
因为3-2处理过了对吧,由dp[5][1]而来的时候,为什么加的是dp[3][0]呢?(请注意观察式子,可以用笔推导),因为前面有提到过,若要根与子树相连,必须选取根与子树的根的边。所以整个式子
就很好理解,你当前的这个点的儿子取j条边,你的根就只能取m-j-1条边。
代码实现如下:
#include<iostream> #include<vector> #include<cstring> using namespace std; int dp[105][105]; vector<int>son[105]; int val[105][105]; int vis[105]; int n,m; void dfs(int x) { int len=son[x].size(); vis[x]=1;///经过的点打上标记,不再处理 for(int i=0;i<len;i++) { int y=son[x][i]; if(vis[y]==1) { continue; } vis[y]=1;///谁先到谁是爹 dfs(y); for(int j=m;j>=1;j--) { for(int k=j-1;k>=0;k--) { dp[x][j]=max(dp[x][j],val[x][y]+dp[y][k]+dp[x][j-k-1]);///文中解释 } } } } int main() { cin>>n>>m; memset(vis,0,sizeof(vis)); for(int i=1;i<n;i++) { int u,v,w; cin>>u>>v>>w; son[u].push_back(v); son[v].push_back(u); ///这里建双向边,主要是不知道哪个是父亲,所以添加双向边。 val[u][v]=w; val[v][u]=w; ///这里我与网上的做法不同,他们喜欢把值赋予节点上,那样子不好理解,这样的赋值可以直观看出他就是边权 } dfs(1);///从根节点开始 cout<<dp[1][m]<<endl;///回溯至最开始的根节点,m条边的最大值 }