hihocoder #1055 : 刷油漆(树形dp)

当一个节点被选择后,它的所有祖先节点也要被选择

该条件换一个说法,可以解释为:只有当选择了一个节点后,我们才可以选择它的子节点。

我们首先建立状态f[i][k]f[i][k]表示以i节点为根的子树,在满足条件一的情况下,选择至多k的节点能够得到的最大权值。

则可以写出状态转移情况:

  • 选择i节点:f[i][k]等于w[i]加上所有子节点选择k-1个节点的最大权值
  • 不选择i节点:f[i][k] = 0

不选择i节点很好处理,那么我们如何处理选择i节点时,子节点的情况?

根据题目描述,我们知道给定的节点i可能有多个儿子节点,不妨假设其有m个儿子节点。

则可以表示为,在m个儿子上一共选择k-1个节点,使得选择的节点权值之和最大。

举个例子,比如样例:

样例的树结构

我们在计算f[1][4]时,需要考虑对于以2,3,4分别为根节点子树,一共选择3个节点,来使得权值最大。也就是说在2子树中选择x个节点,3子树中选择y个节点,4子树中选择z个节点,使得x+y+z=3

很显然,该问题同样是一个动态规划问题:

建立状态g[i][j]g[i][j]表示前i个儿子选择j个节点时最大权值,则有状态转移过程:

g[i][j] = max{g[i - 1][j - k] + f[childId][k] | k = 0 .. j}
时间限制: 10000ms
单点时限: 1000ms
内存限制: 256MB
描述

上回说到,小Ho有着一棵灰常好玩的树玩具!这棵树玩具是由N个小球和N-1根木棍拼凑而成,这N个小球都被小Ho标上了不同的数字,并且这些数字都是处于1..N的范围之内,每根木棍都连接着两个不同的小球,并且保证任意两个小球间都不存在两条不同的路径可以互相到达。没错,这次说的还是这棵树玩具的故事!

小Ho的树玩具的质量似乎不是很好,短短玩了几个星期,便掉漆了!

“简直是一场噩梦!”小Ho拿着树玩具眼含热泪道。

“这有什么好忧伤的,自己买点油漆刷一刷不就行了?”小Hi表示不能理解。

“还可以这样?”小Ho顿时兴高采烈了起来,立马跑出去买回来了油漆,但是小Ho身上的钱却不够——于是他只买回了有限的油漆,这些油漆最多能给M个结点涂上颜色,这就意味着小Ho不能够将他心爱的树玩具中的每一个结点都涂上油漆!

小Ho低头思索了半天——他既不想只选一部分结点补漆,也不想找小Hi借钱,但是很快,他想出了一个非常棒的主意:将包含1号结点的一部分连通的结点进行涂漆(这里的连通指的是这一些涂漆的结点可以互相到达并且不会经过没有涂漆的结点),然后将剩下的结点拆掉!

那么究竟选择哪些结点进行涂漆呢?小Ho想了想给每个结点都评上了分——他希望最后留下来,也就是涂漆了的那些结点的评分之和可以尽可能的高!

那么,小Ho该如何做呢?

提示一:树上的动态规划?其实老早就接触过了吧!

输入

每个测试点(输入文件)有且仅有一组测试数据。

每组测试数据的第一行为两个整数N、M,意义如前文所述。

每组测试数据的第二行为N个整数,其中第i个整数Vi表示标号为i的结点的评分

每组测试数据的第3~N+1行,每行分别描述一根木棍,其中第i+1行为两个整数Ai,Bi,表示第i根木棍连接的两个小球的编号。

对于100%的数据,满足N<=10^2,1<=Ai<=N, 1<=Bi<=N, 1<=Vi<=10^3, 1<=M<=N

小Hi的Tip:那些用数组存储树边的记得要开两倍大小哦!

输出

对于每组测试数据,输出一个整数Ans,表示使得涂漆结点的评分之和最高可能是多少。



样例输入
10 4
370 328 750 930 604 732 159 167 945 210 
1 2
2 3
1 4
1 5
4 6
4 7
4 8
6 9
5 10
样例输出
2977

当一个节点被选择后,它的所有祖先节点也要被选择

该条件换一个说法,可以解释为:只有当选择了一个节点后,我们才可以选择它的子节点。

我们首先建立状态f[i][k]f[i][k]表示以i节点为根的子树,在满足条件一的情况下,选择至多k的节点能够得到的最大权值。

则可以写出状态转移情况:

  • 选择i节点:f[i][k]等于w[i]加上所有子节点选择k-1个节点的最大权值
  • 不选择i节点:f[i][k] = 0

不选择i节点很好处理,那么我们如何处理选择i节点时,子节点的情况?

根据题目描述,我们知道给定的节点i可能有多个儿子节点,不妨假设其有m个儿子节点。

则可以表示为,在m个儿子上一共选择k-1个节点,使得选择的节点权值之和最大。

举个例子,比如样例:

样例的树结构

我们在计算f[1][4]时,需要考虑对于以2,3,4分别为根节点子树,一共选择3个节点,来使得权值最大。也就是说在2子树中选择x个节点,3子树中选择y个节点,4子树中选择z个节点,使得x+y+z=3

很显然,该问题同样是一个动态规划问题:

建立状态g[i][j]g[i][j]表示前i个儿子选择j个节点时最大权值,则有状态转移过程:

g[i][j] = max{g[i - 1][j - k] + f[childId][k] | k = 0 .. j}

因此我们可以得到整个动态规划的过程:

// 初始化f数组
f[][] = -1;

dp(root, k):
    If (k == 0) Then
        Return 0;
    End If
    If (f[root][k] != -1) Then
        // 由于可能多次调用dp(root,k)
        // 所以这里采用了记忆化的思想
        Return f[root][k];
    End If
    // 不选择该节点
    f[root][k] = 0;
    // 选择该节点
    g[][] = 0;  // 初始化g数组,需要注意g为该函数的局部变量
    For i = 1 .. m  // 枚举子节点
        For j = 0 .. k - 1
            For t = 0 .. j
                If g[i][j] < dp(child[i], t) + g[i-1][j-t] Then
                    g[i][j] = dp(child[i], t) + g[i-1][j-t];
                End If
            End For
        End For
    End For
    If (f[root][k] < g[m][k-1] + w[root]) Then
        f[root][k] = g[m][k-1] + w[root];
    End If
    Return f[root][k];

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;

const int maxn = 110;
int n,m,cnt,f[maxn][maxn];
int score[maxn],pre[maxn];
struct Edge
{
	int key,next;
}edge[maxn<<1];

void addedge(int x,int y)
{
	edge[cnt].key = y;
	edge[cnt].next = pre[x];
	pre[x] = cnt++;
}

void dfsDP(int cur,int p,int M)
{
	if(M == 0)
	{
		f[cur][M] = 0;
		return;
	}
	if(f[cur][M] != -1) return;
	int num = 0,g[maxn][maxn] = {0};
	for(int i = pre[cur]; i != -1; i = edge[i].next)
	{
		int v = edge[i].key;
		if(v == p) continue;
		num++;
		for(int j = 0; j < M; j++)
			for(int t = 0; t <= j; t++)
			{
				dfsDP(v,cur,t);
				if(g[num][j] < f[v][t] + g[num-1][j-t])
					g[num][j] = f[v][t] + g[num-1][j-t];
			}
	}
	if(f[cur][M] < g[num][M-1] + score[cur])
		f[cur][M] = g[num][M-1] + score[cur];
}

int main()
{	
	while(scanf("%d%d",&n,&m)!=EOF)
	{
		for(int i = 1; i <= n; i++)
			scanf("%d",&score[i]);
		memset(f,-1,sizeof(f));
		memset(pre,-1,sizeof(pre));
		cnt = 0;
		for(int i = 1; i < n; i++)
		{
			int a,b;
			scanf("%d%d",&a,&b);
			addedge(a,b);
			addedge(b,a);
		}
		dfsDP(1,0,m);
		printf("%d\n",f[1][m]);
	}
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值