题目大意
给出一棵带点权的树,要选中若干个结点,把被选中的结点到根结点(
1
1
1号结点)路径上所有结点(包括被选中的结点和根结点)打上标记,给出被打上标记的结点的数量上限,求最大化的被打上标记的结点的权值和。
对于前
20
%
20\%
20%的数据,
1
≤
树的结点个数
,
被打上标记的结点的数量上限
≤
10
1 \leq \text{树的结点个数} , \text{被打上标记的结点的数量上限} \leq 10
1≤树的结点个数,被打上标记的结点的数量上限≤10;
对于前
60
%
60\%
60%的数据,
1
≤
树的结点个数
,
被打上标记的结点的数量上限
≤
100
1 \leq \text{树的结点个数} , \text{被打上标记的结点的数量上限} \leq 100
1≤树的结点个数,被打上标记的结点的数量上限≤100;
对于
100
%
100\%
100%的数据,
1
≤
树的结点个数
,
被打上标记的结点的数量上限
≤
3000
1 \leq \text{树的结点个数} , \text{被打上标记的结点的数量上限} \leq 3000
1≤树的结点个数,被打上标记的结点的数量上限≤3000,
∣
点的权值
∣
≤
1
0
5
| \text{点的权值} | \leq 10^5
∣点的权值∣≤105。数据有梯度,保证给出的是合法的树。
分析
这是一道树形动态规划(树形
D
P
DP
DP)题。
我们定义
f
i
,
j
f_{i,j}
fi,j(
1
≤
i
≤
树的结点个数
1 \leq i \leq \text{树的结点个数}
1≤i≤树的结点个数,
1
≤
j
≤
以
i
为根的子树的大小
1 \leq j \leq \text{以} i \text{为根的子树的大小}
1≤j≤以i为根的子树的大小)为以结点
i
i
i为根的子树中,被打上标记的结点的数量上限为
j
j
j时求最大化的被打上标记的结点的权值和。尽管题目让我们求的是到根结点的路径,要用的是结点的父亲,但只要我们满足
j
≥
1
j \geq 1
j≥1,就能保证被选中的结点到根结点上所有结点(包括被选中的结点和根结点)都被算到。
显然
f
x
,
1
=
点
x
的权值
f_{x,1}= \text{点} x \text{的权值}
fx,1=点x的权值,那么怎么求出
f
x
,
i
f_{x,i}
fx,i(
2
≤
i
≤
以
x
为根的子树的大小
2 \leq i \leq \text{以} x \text{为根的子树的大小}
2≤i≤以x为根的子树的大小)呢?我们可以用一种类似背包问题的解法的方法。将
f
x
,
i
f_{x,i}
fx,i(
i
=
2
,
3
,
⋯
,
以
x
为根的子树的大小
i=2,3, \cdots , \text{以} x \text{为根的子树的大小}
i=2,3,⋯,以x为根的子树的大小)设为
−
∞
- \infty
−∞,并设
s
=
1
s=1
s=1,然后依次枚举结点
x
x
x的儿子。设结点
y
y
y为当前枚举到的儿子,我们将
f
x
,
j
+
k
f_{x,j+k}
fx,j+k更新为
max
{
f
x
,
j
+
k
,
f
x
,
j
+
f
y
,
k
}
\max \{ f_{x,j+k},f_{x,j}+f_{y,k} \}
max{fx,j+k,fx,j+fy,k}(其中
j
j
j从
s
s
s向下枚举到
1
1
1,
k
k
k从以
y
y
y为根的子树的大小向下枚举到
1
1
1),并把
s
s
s加上以
y
y
y为根的子树的大小。有了
f
f
f后,我们可以很快求出答案了:答案为
max
{
f
1
,
i
∣
1
≤
i
≤
被打上标记的结点的数量上限
}
\max \{ f_{1,i}|1 \leq i \leq \text{被打上标记的结点的数量上限} \}
max{f1,i∣1≤i≤被打上标记的结点的数量上限}。
根据我们的思路,可以写出以下代码:
#include<cstdio>
int max(int x,int y)//最大值函数
{
if(x>y)
{
return x;
}
return y;
}
int len;struct edge{int to/*终点*/,next/*下一条边*/;}e[6006];//边
struct node{int v,last/*连出的最后一条边*/,f[3003],s/*s和子树大小*/;}a[3003];//点
void link(int x,int y)//连边函数
{
++len;
e[len].to=y;
e[len].next=a[x].last;
a[x].last=len;
return;
}
int n;
void dfs(int x/*当前结点*/,int fa/*当前结点的父亲*/)
{
a[x].s=1;//初始化s
a[x].f[1]=a[x].v;//初始化f
for(int i=2;i<=n;i++)
{
a[x].f[i]=-2147483647;
}
for(int i=a[x].last;i!=-1;i=e[i].next)/遍历每一条出边
{
int y=e[i].to;
if(y!=fa)//如果y不是x的父亲(即y是x的儿子)时
{
dfs(y,x);//递归
for(int j=a[x].s;j>=1;j--)//更新f
{
for(int k=a[y].s;k>=1;k--)
{
a[x].f[j+k]=max(a[x].f[j+k],a[x].f[j]+a[y].f[k]);
}
}
a[x].s+=a[y].s;//更新s
}
}
return;
}
int main()
{
freopen("tree.in","r",stdin);//注意要开文件输入输出
freopen("tree.out","w",stdout);
int lim;
scanf("%d%d",&n,&lim);//读入n,lim
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i].v);//读入v[i]
a[i].last=-1;//初始化a
}
len=0;
for(int i=1;i<n;i++)
{
int x,y;
scanf("%d%d",&x,&y);//读入边
link(x,y);//连边(注意树是无向图,要连双向边)
link(y,x);
}
dfs(1,-1);//求出f
int ans=-2147483647;//求出答案
for(int i=1;i<=lim;i++)
{
ans=max(ans,a[1].f[i]);
}
printf("%d",ans);//输出答案
return 0;
}
总结
做树形 D P DP DP的题目时,一般要用到结点之间的父子关系,我们要先找出树上的父子关系,然后根据推出来的父子间数量的关系得出答案。