Typesetting math: 5%

『没有上司的舞会 树形DP』

Parsnip·2018-12-14 16:33·289 次阅读

『没有上司的舞会 树形DP』

<更新提示>

<第一次更新>


<正文>

树形DP入门#

有些时候,我们需要在树形结构上进行动态规划来求解最优解。

例如,给定一颗NN个节点的树(通常是无根树,即有N1条无向边),我们可以选择任意节点作为根节点从而定义出每一颗子树的深度,形成一个子问题重叠的结构,是符合动态规划前提的。在设计动态规划算法时,一般由节点由深到浅的顺序来作为DP的阶段。DP的状态表示中,数组的第一维通常表示子树根节点的编号。大多数时候,我们用递归的形式实现树形动态规划。先在它的每个子节点上递归求出最优解,再在返回时求解当前节点的最优解。

下面我们通过一道入门例题来讲解。

没有上司的舞会(TYVJ1052 CODEVS1380)#

Description#

Ural大学有N个职员,编号为1~N。他们有从属关系,也就是说他们的关系就像一棵以校长为根的树,父结点就是子结点的直接上司。每个职员有一个快乐指数。现在有个周年庆宴会,要求与会职员的快乐指数最大。但是,没有职员愿和直接上司一起与会。

Input Format#

第一行一个整数N。(1<=N<=6000)

接下来N行,第i+1行表示i号职员的快乐指数Ri。(-128<=Ri<=127)

接下来N-1行,每行输入一对整数L,K。表示K是L的直接上司。

最后一行输入0,0。

Output Format#

输出最大的快乐指数。

Sample Input#

Copy
7 1 1 1 1 1 1 1 1 3 2 3 6 4 7 4 4 5 3 5 0 0

Sample Output#

Copy
5

Hint#

Limitation#

各个测试点1s

解析#

通过分析可以发现,职员和上司之间的关系可以看作为是一个树形结构,又是最优解问题,可以尝试树形DP
根据之前我们对树形DP的初步了解,我们先设置DP的状态,并将根节点编号作为第一维。分析题意,我们发现一个人是否参加舞会只和他的直接上司有关,所以我们可以把第二维设为0/1,这样,f[i][0/1]代表以i号节点为根节点的子树结构中的最大快乐指数值之和,0代表i号职员不参加晚会,1代表i号职员参加晚会,这样的状态,是满足最优子结构的。
利用阶段和之前的状态建立关系。如果i参加晚会,则他的下属不能参加晚会,如果他不参加晚会,则他的下属可以参加晚会,当然,也可以不参加。 那么,我们就可以写出状态转移方程:

1.f[i][1]=h[i]+sSon(i)f[s][0]2.f[i][0]=sSon(i)max(f[s][1],f[s][0])

其中,Son(i)代表节点i的子节点集合。
本题当中,我们还需要找到有根树的根,即没有上司的职员,然后从他开始进行DP求解。最终,DP的目标状态是max(f[root][1],f[root][0])。时间复杂度为O(n)

Code:

Copy
#include<bits/stdc++.h> using namespace std; const int N=6000+80; int n,h[N]={},f[N][2]={},vis[N]={}; vector < int > son[N]; inline void read(int &k) { int x=0,w=0;char ch; while(!isdigit(ch))w|=ch=='-',ch=getchar(); while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar(); k=(w?-x:x);return; } inline void input(void) { read(n); for(int i=1;i<=n;i++)read(*(h+i)); for(int i=1;i<n;i++) { int f,s; read(s),read(f); vis[s]=true; son[f].push_back(s); } } inline void dp(int root) { f[root][0]=0;f[root][1]=h[root]; for(int i=0;i<son[root].size();i++) { int S=son[root][i]; dp(S); f[root][0]+=max(f[S][1],f[S][0]); f[root][1]+=f[S][0]; } } inline void solve(void) { memset(f,0,sizeof f); int root; for(int i=1;i<=n;i++) { if(!vis[i]){root=i;break;} } dp(root); printf("%d\n",max(f[root][0],f[root][1])); } int main(void) { freopen("test.in","r",stdin); freopen("test.out","w",stdout); input(); solve(); return 0; }

总结#

通过一道例题,我们对树形DP建立的初步的认识。但是,在更复杂的题目中,我们需要学习更多的技巧,例如,出来自顶向下的递归以外,我们还可以运用图上DP的方法,用自底向上的拓扑序来执行树形DP,但是实现更为复杂,通常来说,这样的DP已经足够。
在更多的题目中,树是以一张N个点,N-1条边的无向图的形式给出的,这样的话我们还需要找出树的根,一般是用深搜的方法。


<后记>

posted @ 2018-12-14 16:33  Parsnip  阅读(289)  评论(0)    收藏  举报
编辑推荐:
· AES 加密模式演进:从 ECB、CBC 到 GCM 的 C# 深度实践
· InnoDB为什么不用跳表,Redis为什么不用B+树?
· 记一次 C# 平台调用中因非托管 union 类型导致的内存访问越界
· [EF Core]聊聊“复合”属性
· 那些被推迟的 C# 14 特性及其背后的故事
阅读排行:
· 博客园出海记-开篇:扬帆启航
· 微软开源的 MCP 教程「GitHub 热点速览」
· 记一次 .NET 某汽车控制焊接软件 卡死分析
· 关于布尔类型的变量不要加 is 前缀,被网友们吐槽了,特来完善下
· C#中的多级缓存架构设计与实现深度解析
点击右上角即可分享
微信分享提示
目录