树形dp

题目链接:
选课
积蓄程度

算法分析:
这两道题目,选课是经典的树上背包问题,是分组背包的树上模型。积蓄程度是不定根树形dp问题。书上介绍地很详细。这里主要介绍下代码中的注意事项。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
int n, m, f[310][310], score[310];
struct node
{
	int next, to;
}edg[310];
int h[310], cnt;
inline void add(int u, int v)
{
	++cnt;
	edg[cnt].next = h[u];
	edg[cnt].to = v;
	h[u] = cnt;
}
void dfs(int u)
{
	f[u][1] = score[u];
	for (int i = h[u]; i; i = edg[i].next)
	{
		int v = edg[i].to;
		dfs(v);
		for (int j = m; j >= 1; --j)  // 逆序能够保证此时f[u][j-k]不包括v这颗子树  
			for (int k = 0; k < j; ++k)
			f[u][j] = max(f[u][j], f[v][k] + f[u][j-k]);
	} 
}
int main()
{
	scanf("%d%d", &n, &m);
	int fa;
	for (int i = 1; i <= n; ++i)
	{
		scanf("%d%d", &fa, &score[i]);
		add(fa, i);
	}
	// 0号点是根结点 
	++m;  // 多加了个0号点,选择数目加1  
	memset(f, 0xcf, sizeof(f));
	dfs(0);
	printf("%d\n", f[0][m]); 
	return 0;
}


反思与总结:
1.题目模型是森林,要构成树的话,需要额外增加结点,连向各棵树的根,这个额外结点为0号点。此时++m。

2.因为是从树根开始遍历,存图的时候,可以当做有向图存。

3.对于以下代码:
f [ u ] [ j ] = m a x ( f [ u ] [ j ] , f [ v ] [ k ] + f [ u ] [ j − k ] ) f[u][j] = max(f[u][j], f[v][k] + f[u][j-k]) f[u][j]=max(f[u][j],f[v][k]+f[u][jk])
注意体会dfs的过程,当v这个分支遍历结束的时候,此时 f [ v ] [ k ] f[v][k] f[v][k]表示的是v这个分支中取体积为k的物品, f [ u ] [ j − k ] f[u][j-k] f[u][jk]表示的是已经遍历过的而不包含v这个分支的部分中体积为 j − k j-k jk的物品。要保证 f [ u ] [ j − k ] f[u][j-k] f[u][jk]不包含v这个分支,只能让m的那层循环逆序。类似01背包的逆序。

以下是积蓄程度的代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
int n, d[200010], f[200010], deg[200010];
struct node
{
	int next, to, val;
}edg[400010];
int h[200010], cnt;
inline void add(int u, int v, int val)
{
	++cnt;
	edg[cnt].next = h[u];
	edg[cnt].to = v;
	edg[cnt].val = val;
	h[u] = cnt;
}
void dfs1(int u, int fa)
{
	d[u] = 0;
	for (int i = h[u]; i; i = edg[i].next)
	{
		int v = edg[i].to;
		if (v == fa) continue;
		dfs1(v, u);
		if (deg[v] == 1) d[u] += edg[i].val;
		else d[u] = d[u] + min(edg[i].val, d[v]);
	}
} 
void dfs2(int u, int fa)
{
	for (int i = h[u]; i; i = edg[i].next)
	{
		int v = edg[i].to;
		if (v == fa) continue;
		if (deg[u] == 1) f[v] = d[v] + edg[i].val;
		else f[v] = d[v] + min(edg[i].val, f[u]-min(d[v], edg[i].val));
		dfs2(v, u);
	}
} 
int main()
{
	int T;
	scanf("%d", &T);
	while (T--)
	{
		scanf("%d", &n);
		// 链式前向星的初始化,只要初始化cnt和h就可以了  
		cnt = 0;
		memset(h, 0, sizeof(h));   
		memset(deg, 0, sizeof(deg));
		int x, y, z;
		for (int i = 1; i < n; ++i)
		{
			scanf("%d%d%d", &x, &y, &z);
			add(x, y, z); add(y, x, z);
			++deg[x]; ++deg[y];
		}
		dfs1(1, 0); // 计算d[x]  
		 
		f[1] = d[1];
		dfs2(1, 0); // 计算f[x]  
		int ans = 0;
		for (int i = 1; i <= n; ++i) if (ans < f[i]) ans = f[i];
		printf("%d\n", ans);
	}
	return 0;
}

反思与总结:
1.因为是多组数据,要打扫战场。对于链式前向星存图,想要初始化,只要初始化cnt和h就可以了。

2.应特别考虑叶子结点的情况,可以用一个数组deg表示每个结点的度,度为1的就是叶子结点。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值