集训-打怪兽(树形DP)
2017-04-18 07:45
204 查看
打怪兽
题目描述有一棵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的儿子时,每增加一个新的儿子,状态很复杂,举个例子更好理解一点。
假设3就是新的儿子状态 dp[3][j][0]全部都算出来了,那状态的转移有两种,
情况1:就把之前所有儿子不回到根节点的状态(这里之前的所有儿子实际只有2这个节点)是,加上新儿子回到根节点的情况(因为你要有一种情况回到根节点,才能继续走啊)
情况2:就把之前所有儿子回到根节点的状态,加上新儿子不会回到根节的状态的情况。(理由同上)
可以发现转移都需要回到根节点的状态。
那么问题就变为如何求回到根节点的状态了。
dp[i][j][1]表示以i为根节点,走j步,也同样只在子树,但是最后要回到根节点的状态。
for(int j=step;j>=0;j--)
4000
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<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<vector>
using namespace std;
#define Maxn 102
#define Maxs 102
vector<int> 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<len;i++)if(f[root][i]!=pre)
{
int v=f[root][i];
dfs(v,root);
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]);
}
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<n;i++)
{
int x,y;
scanf("%d%d",&x,&y);
f[x].push_back(y);
f[y].push_back(x);
}
dfs(1,0);
printf("%d",dp[1][step][0]);
return 0;
}
相关文章推荐
- 【NOIP2017提高A组集训10.25】摘Galo (树形dp)
- 「雅礼集训 2017 Day2」水箱 并查集+树形DP
- 【NOIP2016提高A组集训第14场11.12】随机游走——期望+树形DP
- 集训-蚂蚁聚会(树形DP)
- 【2016北京集训测试赛(二)】 thr (树形DP)
- 集训-vijos选课(树形DP)
- 集训-移动信号(树形DP)
- 集训-可割点(树形DP)
- [jzoj]5484. 【清华集训2017模拟11.26】快乐树(树形DP)
- 陕西省集训之树形dp
- HDU 4274 spy work (树形DP)
- 计蒜客: 最大的快乐指数(树形DP)
- poj 2486 Apple Tree 树形DP,树形背包
- SPFA + 树形DP:The Ghost Blows Light
- poj 1155 TELE (树形背包dp)
- FAFUOJ 1572 Big castle(树形DP)
- 树形dp(IOI 2005河流代码理解)
- 【BZOJ1812】[Ioi2005]riv 树形DP
- hdu 1011 树形DP
- 中石油 暑期集训个人赛 DP部分