集训-打怪兽(树形DP)

打怪兽

题目描述

有一棵N个结点的树,结点编号1至N。第i个结点有s[i]只怪兽。现在你要从第1个结点出发,最多走STEP步(每一步就是走一条边),当你到达一个结点时,你就可以把该结点的怪兽全部打死。
现在问题是:在最优策略下,你最多可以打死多少只怪物?注意:可以多次经过同一个结点,但是该结点的怪物被打死后,该结点就没有怪物了。

输入格式  1795.in

第一行,两个整数: N和STEP。 1 <= N <= 50, 1 <= STEP <= 100。
第二行,共N个整数,第i个整数是s[i]。 1 <= s[i] <= 100。
接下来有N-1行,第i行是两个整数:a和b,表示结点a和结点b之间有一条无向边。

输出格式  1795.out

一个整数。

输入样例  1795.in
输入样例一:
2 6
7 1 
1 2
输入样例二:
3 5
2 3 9 
3 2
1 2
输入样例三:
5 3
6 1 6 4 4 
1 4
2 4
5 1
3 4
输入样例四:
10 4
4 2 1 6 3 7 8 5 2 9 
9 6
1 6
7 9
10 6
5 6
8 1
3 1
4 6
2 6
输入样例五:
50 48
6 9 4 9 5 8 6 4 4 1 4 8 3 4 5 8 5 6 4 9 7 9 7 9 5 2 7 2 7 7 5 9 5 8 5 7 1 9 3 9 3 6 4 5 5 4 7 9 2 2 
25 5
22 5
35 25
42 25
40 25
9 42
32 25
12 40
37 5
44 35
23 25
1 32
24 42
28 9
20 32
4 23
26 40
33 25
11 20
48 33
34 26
6 37
16 12
50 1
46 48
17 24
8 22
43 25
18 11
30 24
31 48
36 34
39 18
13 9
10 50
45 42
3 16
47 40
15 1
2 10
29 47
19 22
7 48
14 44
41 48
49 1
38 4
27 46
21 47
输出样例  1795.out

输出样例一:
8
输出样例二:
14
输出样例三:
16
输出样例四:
26
输出样例五:

194


很明显的树形DP。

我们知道最后可以不回到根节点。所以我们设一个状态dp[i][j][0]表示以i为根节点,走j步(到子树),最后不回到根节点可以杀死的最大小怪兽数。


然而,直接求这个dp[i][j][0]是非常困难的,在枚举i的儿子时,每增加一个新的儿子,状态很复杂,举个例子更好理解一点。

集训-打怪兽(树形DP)_第1张图片

假设3就是新的儿子状态 dp[3][j][0]全部都算出来了,那状态的转移有两种,

情况1:就把之前所有儿子不回到根节点的状态(这里之前的所有儿子实际只有2这个节点)是,加上新儿子回到根节点的情况(因为你要有一种情况回到根节点,才能继续走啊)

情况2:就把之前所有儿子回到根节点的状态,加上新儿子不会回到根节的状态的情况。(理由同上)

可以发现转移都需要回到根节点的状态。

那么问题就变为如何求回到根节点的状态了。

dp[i][j][1]表示以i为根节点,走j步,也同样只在子树,但是最后要回到根节点的状态。

for(int j=step;j>=0;j--) 

        for(int k=2;k<=j;k++)dp[root][j][1]=max(dp[root][j][1],dp[root][j-k][1]+dp[v][k-2][1]);

只要理解了上面的式子就差不多了。

j是枚举步数,要在旧的儿子这边走j-k步,最后回到根节点。加号右边的式子为什么k要-2呢?因为从root节点到他的儿子结点v,因为儿子这边是要回来根节点的,那么从根节点走到儿子结点,再从儿子结点回来根节点,这就消耗了两步了,所以说只剩下k-2步了。

还要注意一个小细节,j要从大的枚举到小的,因为新的状态不能重复计算新的儿子,所以这里是用滚动的,必须从大到小(不理解的等会看看代码可能就懂了)

既然dp[i][j][1]求出来了,那么dp[i][j][0]也可以求了。

        for(int j=step;j>=0;j--)
for(int k=1;k<=j;k++)
{
dp[root][j][0]=max(dp[root][j][0],dp[v][k-1][0]+dp[root][j-k][1]);
if(k>1)
dp[root][j][0]=max(dp[root][j][0],dp[v][k-2][1]+dp[root][j-k][0]);
}

第一个语句的意思是这样的:从之前的儿子的回到根节点状态走j-k步,然后再从跟根结点走k-1步(因为不用回来,但是还是要从根走到儿子需要1步)。

第二个语句的意思:从新的儿子走k步最后回来,再走j-k步不会来的。

注意:也用了滚动。

dp就完成了,最后的答案就是dp[1][step][0]。

还需要注意的:求dp[i][j][1]的这段代码必须写在dp[i][j][0]的后面,因为他们都是滚动,而dp[i][j][1]不依赖其他,但是dp[i][j][0]依赖dp[i][j][1],如果先算dp[i][j][1]那么会出现重复拿一个点的情况,详看代码。

code:

#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define Maxn 102
#define Maxs 102
vector f[Maxn];
int dp[Maxn][Maxs][2];
int n,step;
int s[Maxn];
void dfs(int root,int pre)
{
	int len=f[root].size();
	for(int i=0;i=0;j--)
			for(int k=1;k<=j;k++)
			{
				dp[root][j][0]=max(dp[root][j][0],dp[v][k-1][0]+dp[root][j-k][1]);
				if(k>1)
				 dp[root][j][0]=max(dp[root][j][0],dp[v][k-2][1]+dp[root][j-k][0]);
			} 	
		for(int j=step;j>=0;j--) 
        	for(int k=2;k<=j;k++)
            	dp[root][j][1]=max(dp[root][j][1],dp[root][j-k][1]+dp[v][k-2][1]);
	}
	for(int i=0;i<=step;i++)
	{
		dp[root][i][0]+=s[root];
		dp[root][i][1]+=s[root];
	}	
	return;
}
int main()
{
	freopen("1795.in","r",stdin);
	freopen("1795.out","w",stdout);
	scanf("%d%d",&n,&step);
	for(int i=1;i<=n;i++)scanf("%d",&s[i]);
	for(int i=1;i


你可能感兴趣的:(日常水)