Description
给一棵n 个结点的有根树,结点由1 到n 标号,根结点的标号为1。每个结点上有一个物品,第i 个结点上的物品价值为vi。
你需要从所有结点中选出若干个结点,使得对于任意一个被选中的结点,其到根的路径上所有的点都被选中,并且选中结点的个数不能超过给定的上限lim。在此前提下,你需要最大化选中结点上物品的价值之和。求这个最大的价值之和。
Data Constraint
对于前20% 的数据,1<=n; lim<=10
对于前60% 的数据,1<=n; lim<=100
对于100% 的数据,1<=n; lim<=3000; |vi| <=10^5 数据有梯度,保证给出的是合法的树。
Solution
我们设f[i][j]为当前以i为根的子树选了j个节点,直接做一下背包问题就好。只可惜我在赛场上没考虑到背包的后效性……
代码
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
const int maxn=3005;
int f[maxn][maxn],n,i,num,t,j,k,l,m,first[maxn],last[maxn*2];
int next[maxn*2],a[maxn],size[maxn],ans,x,y;
void lian(int x,int y){
last[++num]=y;next[num]=first[x];first[x]=num;
}
void dg(int x,int y){
int t,p,q;f[x][0]=0,f[x][1]=a[x];size[x]++;
for (t=first[x];t;t=next[t]){
if (last[t]==y) continue;
dg(last[t],x);
size[x]+=size[last[t]];
p=min(size[x],m);
for (i=p;i>=0;i--){
q=min(size[last[t]],i-1);
for (j=1;j<=q;j++)
f[x][i]=max(f[x][i],f[x][i-j]+f[last[t]][j]);
}
}
}
int main(){
freopen("tree.in","r",stdin);freopen("tree.out","w",stdout);
//freopen("data.in","r",stdin);
scanf("%d%d",&n,&m);
for (i=1;i<=n;i++)
scanf("%d",&a[i]);
for (i=1;i<n;i++)
scanf("%d%d",&x,&y),lian(x,y),lian(y,x);
memset(f,128,sizeof(f));
dg(1,0);
for (i=1;i<=m;i++)
ans=max(ans,f[1][i]);
printf("%d\n",ans);
}