题目(直接copy了)
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
-5
4
-6
6
9
6
3 2
3 1
2 4
2 5
1 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嘛。。。。然而一时间的脑子卡顿,没有想到这是背包。。。。。(毕竟我基本没打过背包)。
解法
60%:树形dp,有限背包。设
fi,j
表示以i为根节点的子树选了j个能获得的最大价值,然后就有
fi,j=max(fi,min(size[i′],lim)+fson,min(i−1,size[son]))
size[x]表示以x为节点的子树的大小。
100%:上述方法可以水过,实际复杂度没有那么高。
代码
#include<cstdio>
#include<algorithm>
#include<cstring>
#define fo(i,a,b) for(int i=a;i<=b;i++)
using namespace std;
const int maxn=3005;
int n,lim,ans,num;
int next[2*maxn],go[2*maxn],fre[maxn],a[maxn],f[maxn][maxn],size[maxn];
void add(int x,int y)
{
go[++num]=y;
next[num]=fre[x];
fre[x]=num;
}
void dfs(int x,int dad)
{
f[x][0]=0; f[x][1]=a[x];
int u=fre[x];
while (u){
bool bo=false;
if (go[u]!=dad) {
dfs(go[u],x);
size[x]+=size[go[u]];
bo=true;
}
if (bo) {
for(int i=min(size[x],lim);i>=1;i--){
fo(j,0,min(i-1,size[go[u]]))
if (i-j>=0)
f[x][i]=max(f[x][i],f[x][i-j]+f[go[u]][j]);
}
}
u=next[u];
}
}
int main()
{
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
scanf("%d%d",&n,&lim);
fo(i,1,n) scanf("%d",&a[i]),size[i]=1;
fo(i,1,n-1){
int x,y;
scanf("%d%d",&x,&y);
add(x,y); add(y,x);
}
memset(f,128,sizeof(f));
dfs(1,0);
fo(i,1,lim) ans=max(ans,f[1][i]);
printf("%d",ans);
fclose(stdin); fclose(stdout);
}