题目描述
有一棵苹果树,如果树枝有分叉,一定是分2叉(就是说没有只有1个儿子的结点)
这棵树共有N个结点(叶子点或者树枝分叉点),编号为1-N,树根编号一定是1。
我们用一根树枝两端连接的结点的编号来描述一根树枝的位置。下面是一颗有4个树枝的树
2 5 \ / 3 4 \ / 1
现在这颗树枝条太多了,需要剪枝。但是一些树枝上长有苹果。
给定需要保留的树枝数量,求出最多能留住多少苹果。
解析
一道很简单的树形dp,然而我调了半天都没调出来,就是菜。
容易看出状态\(dp[x][i]\)表示以\(x\)为根的子树保留\(i\)条边能留住的最多苹果数。
这题坑点在于,题面其实没讲清楚,实际上我们要找的是剪枝后使得剩下的树枝都能与根节点相连的最优情况。
所以说,我们要在状态转移时体现出这一点。具体来说,就是每次转移我们都要保留至少一条从该根节点到其某个儿子的边。剩下的就是个分组多重背包,没什么好讲的。
状态转移:
\[
dp[x][i]=\max\limits_{y\in tree(x),0<i<=min(size[x],q),0<=j<=min(size[y],i-1)}(dp[x][i],dp[x][i-j-1]+dp[y][j]+v(x,y).edge)
\]
具体解释一下方程含义:首先显然,每个以\(x\)为根的子树能够保留的最多的边数就是其子树大小\(-1\)。\(dp[x][i-j-1]\)是由于我们在\(y\)的子树中取了\(j\)条边,如果再在\(x\)的子树中再取\(i-j\)条边的话,我们不能保证\(x\rightarrow y\)之间有连边,也不能保证\(x\)与另一儿子之间有连边,所以我们给多出来一条边,而这条边显然只能连在\(x\rightarrow y\),保证了可行性。再者,对于上下界如果\(j\)取了\(i\),那么意思就是所有选取的边要不然在\(y\)的子树,要不然在\(x\)的子树,而不会多出哪怕一条边在\(x\rightarrow y\),显然时不可行的。
参考代码
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> #include<string> #include<cstdlib> #include<queue> #include<vector> #define INF 0x3f3f3f3f #define PI acos(-1.0) #define N 210 #define MOD 2520 #define E 1e-12 using namespace std; struct node{ int next,ver,edge; }g[N<<1]; int head[N],tot,n,q,dp[N][N],size[N]; bool v[N]; inline void add(int x,int y,int val) { g[++tot].ver=y,g[tot].edge=val; g[tot].next=head[x],head[x]=tot; } inline void dfs(int x) { size[x]=1; for(int i=head[x];i;i=g[i].next){ int y=g[i].ver,z=g[i].edge; dfs(y); size[x]+=size[y]; for(int t=min(q,size[x]);t>0;--t) for(int j=min(t-1,size[y]);j>=0;--j) dp[x][t]=max(dp[x][t],dp[x][t-j-1]+dp[y][j]+z); } } int main() { scanf("%d%d",&n,&q); for(int i=1;i<n;++i){ int u,v,val; scanf("%d%d%d",&u,&v,&val); add(u,v,val); } dfs(1); cout<<dp[1][q]<<endl; return 0; }