树形DP

鄙人第一次写博客,观感不好请见谅(狗头保命)
管他什么DP来了做法都一样
确定状态定义->明确子问题->确定状态转换方程->找边界->打表验证
树形其实也就只是在树上跑DP而已
一般两种遍历顺序:
1.自上而下
2.自下而上

今天我们先来讨论讨论先是第一种情况:以自上而下的顺序跑DP
洛谷P1352 没有上司的舞会
这玩意是个经典的树形dp

确定状态定义:

每个职员要么来要么不来,两种状态我们很显然可以分开来维护:

f(i,0)为i职员不参加舞会时的最大快乐值
f(i,1)为i职员来参加舞会时的最大快乐值

明确子问题

很显然,某个职员来不来和他的上司来不来有着直接的关系嘛
所以说我们可以通过员工节点来维护他的上司节点
当某位员工的上司不来的时候他可以选择来或者不来
但要是上司来了,那这位员工可就不能来了
所以......

确定状态转化方程

f(i,1)表示i职员参加舞会的最大值,而他参加了他的下属就肯定不会再来参加
所以我们可以得到:

CodeCogsEqn (1)

其中一条就出来了!
那看看该职员不来的情况呢?
当员工i不来参加时,他的下属可以选择来或者不来,而f维护的是最大快乐值
所以我们可以得到:

CodeCogsEqn (2)

好的,这下完整的状态转移方程就解决啦!

确定边界条件

开头也说过,这道题是自下而上的遍历顺序
我们来好好分析一下
在确定状态转移方程的时候,我们知道了,f(i,0)和f(i,1)都是由他的员工,即他的孩子节点来维护的
要是某位员工没孩子(bushi)......
要是某位员工没有下属,即这位员工他是个树上的叶子节点呢?
那该员工来参加是的最大快乐值就只能是他自己的快乐值了,可怜的底层社畜......
综上,我们可以得到:

CodeCogsEqn (2)CodeCogsEqn (3)

CodeCogsEqn (1)CodeCogsEqn (3)

打表验证就不呈现出来啦,自己造一组数据手搓结果就行()

上代码

#include<bits/stdc++.h>
using namespace std;
//const int MAXN=1003;某人第一遍敲的时候RE原因实录...眼是真瞎... 
const int MAXN=6005;
struct node
{
	int fa=0,r=0;
	bool leaf=0;    //注意初始化
	vector<int> son;
}tr[MAXN];
int n,dp[MAXN][2],vis[MAXN],root;
void dfs(int v)
{
	vis[v]=1;
	if(tr[v].leaf)
		return;
	for(int w=0;w<tr[v].son.size();w++)
		if(!vis[tr[v].son[w]]) 
			dfs(tr[v].son[w]);
	dp[v][1]=tr[v].r;
	dp[v][0]=0;
	int child;		//加个中间量读起代码来好读一点() 
	for(int i=0;i<tr[v].son.size();i++)
	{
		child=tr[v].son[i];
		dp[v][1]+=dp[child][0];
		dp[v][0]+=max(dp[child][1],dp[child][0]);
	}
	return;
}
int main()
{
	int l,k;
	cin>>n;
	for(int i=1;i<=n;i++)
		cin>>tr[i].r;
	for(int i=1;i<=n-1;i++)
	{
		cin>>l>>k;
		tr[l].fa=k;
		tr[k].son.emplace_back(l);
	}
	for(int i=1;i<=n;i++)
	{ 
		if(tr[i].fa==0)		//查找根节点 
			root=i;
		if(tr[i].son.size()==0)
		{
			tr[i].leaf=1;
			dp[i][0]=0;
			dp[i][1]=tr[i].r;
		}
	} 
	dfs(root);          //从根节点开始遍历
	cout<<max(dp[root][0],dp[root][1]);
	return 0;
} 

简洁的结尾

posted @ 2025-07-26 22:06  Kaos·Abraham  阅读(38)  评论(0)    收藏  举报