jzoj 4814. 【NOIP2016提高A组五校联考2】tree 树形dp

来源:互联网 发布:日本 常任理事国知乎 编辑:程序博客网 时间:2024/05/18 20:36

Description

       给一棵n 个结点的有根树,结点由1 到n 标号,根结点的标号为1。每个结点上有一个物品,第i 个结点上的物品价值为vi。
       你需要从所有结点中选出若干个结点,使得对于任意一个被选中的结点,其到根的路径上所有的点都被选中,并且选中结点的个数不能超过给定的上限lim。在此前提下,你需要最大化选中结点上物品的价值之和。
       求这个最大的价值之和。

Input

第一行为两个整数n; lim
接下来n 行,第i 行包含一个整数vi,表示结点i 上物品的价值。
接下来n- 1 行,每行包含两个整数u; v, 描述一条连接u; v 结点的树边。

Output

输出一行答案。

Sample Input

6 4-54-66963 23 12 42 51 6

Sample Output

2

Data Constraint

对于前20% 的数据,1<=n; lim<=10
对于前60% 的数据,1<=n; lim<=100
对于100% 的数据,1<=n; lim<=3000; |vi| <=10^5 数据有梯度,保证给出的是合法的树。

分析:这题跟那道很经典的树形dp题选课基本上是一毛一样,然后在比赛的时候就把选课的方法打了上去,也就是把这棵树转换成二叉树后O(n^3)dp,本来是想着水个60分滚粗的,但没想到居然A掉了,rp狂掉啊……

这道题的正解是个脑洞蛮大的树形dp,在《背包九讲》里面貌似叫什么泛化物品之类的,反正是蛮神的东西。

先用深搜遍历这棵树,然后用f[i,j]表示搜索到点i的时候,在已经被搜索过的点里面至少选了j个点的最大收益(不包括节点i自己),那么每次从节点i转移的时候我们先把f[i,j]的值全部赋给i的儿子节点,然后转移完儿子节点之后再转移回节点i,由于题目要求,所以转移回节点i的时候要强制选上i的儿子节点。


代码:

#include<iostream>#include<cstdio>#include<cstdlib>#include<cstring>#include<algorithm>#define N 3005#define inf 0x3f3f3f3fusing namespace std;int last[N],val[N],f[N][N],cnt,n,m;struct edge{int to,next;}e[N*2];void addedge(int u,int v){e[++cnt].to=v;e[cnt].next=last[u];last[u]=cnt;e[++cnt].to=u;e[cnt].next=last[v];last[v]=cnt;}void dfs(int x,int fa){for (int i=last[x];i;i=e[i].next){if (e[i].to==fa) continue;for (int j=0;j<=m;j++) f[e[i].to][j]=f[x][j];dfs(e[i].to,x);for (int j=1;j<=m;j++) f[x][j]=max(f[x][j],f[e[i].to][j-1]+val[e[i].to]);}}int main(){freopen("tree.in","r",stdin);freopen("tree.out","w",stdout);scanf("%d%d",&n,&m);for (int i=1;i<=n;i++)scanf("%d",&val[i]);for (int i=1;i<n;i++){int u,v;scanf("%d%d",&u,&v);addedge(u,v);}dfs(1,0);printf("%d",f[1][m-1]+val[1]);return 0;}



0 0