树形dp学习(1)

洛谷P1352 没有上司的舞会

  题干简述如下:
  某大学有 n n n 个职员,编号为 1 1 1 n n n,他们之间有从属关系,也就是说他们的关系就像一棵以校长为根的树,父结点就是子结点的直接上司。每个人 i i i 都有一快乐指数 r i r_i ri,宴会的快乐指数等于与会职员快乐指数之和
  但是,如果某个职员的直接上司来参加舞会,那么该职员就不可能参加舞会,点题“没有上司的舞会”。
  输入:第一行 n n n,接下来 n n n 行表示 r i ( i = 1 , 2 , ⋯   , n ) r_i(i=1,2,\cdots,n) ri(i=1,2,,n),之后输入 n − 1 n-1 n1 对整数对 ( l , k ) (l,k) (l,k) 表示 k k k l l l 的直接上司。
  输出:宴会的最大快乐指数。

前言

  动态规划中,相当一部分是区间规划,但是此题是树形规划。对我来说,此题确实开阔了我的眼界,因为是我首次接触到树形规划。我也是在看这道题的题解时,意识到线性规划不必拘泥于数组,也可以拓展到这种数据结构。树形规划的时间复杂度也没有我想象的那么大,总之这是一道与我而言非常有价值的题目。

解法

  构造数组 d p [ i ] [ j ] ( j = 0 , 1 ) dp[i][j](j=0,1) dp[i][j](j=0,1) j = 0 j=0 j=0 表示当职员 i i i 不参加舞会时,职员 i i i 及其所有下属能够为舞会贡献的最大快乐指数; j = 1 j=1 j=1 表示当职员 i i i 参加舞会时,职员 i i i 及其所有下属能够为舞会贡献的最大快乐指数。
  状态转移方程为:
{ d p [ x ] [ 0 ] = ∑ i = 1 c x max ⁡ { d p [ x i ] [ 0 ] , d p [ x i ] [ 1 ] } d p [ x ] [ 1 ] = ∑ i = 1 c x d p [ x i ] [ 0 ] + r x (1) \begin{cases}dp[x][0]=\displaystyle\sum_{i=1}^{c_x}\max\{dp[x_i][0],dp[x_i][1]\}\\ dp[x][1]=\displaystyle\sum_{i=1}^{c_x}dp[x_i][0]+r_x\end{cases}\tag{1} dp[x][0]=i=1cxmax{dp[xi][0],dp[xi][1]}dp[x][1]=i=1cxdp[xi][0]+rx(1)

  其中 c x c_x cx 表示 x x x直接下属数量, x i x_i xi 表示 x x x 的第 i i i 个直接下属。最优子结构性比较明显,就不在此说明了。按照这个状态转移方程能够很容易地完成代码。

AC代码

# include <iostream>
# include <stdlib.h>

using namespace std;

struct {
	int child,next;
} branch[6005];

struct {
	int r,dad,fbranch,lbranch;
} node[6005];

int n,leader=1,dp[6005][2];

int max_(int a,int b){return a>b?a:b;}

int happy(int code,int par){
	if(dp[code][par])
		return dp[code][par];
	if(par==0){
		int ret=0,br=node[code].fbranch;
		while(br){
			ret+=max_(happy(branch[br].child,0),happy(branch[br].child,1));
			br=branch[br].next;
		}
		return dp[code][par]=ret;
	}
	else{
		int ret=0,br=node[code].fbranch;
		while(br){
			ret+=happy(branch[br].child,0);
			br=branch[br].next;
		}
		return dp[code][par]=ret+node[code].r;
	}
}

int main(){
	cin>>n;
	int l,k;
	for(int i=1;i<=n;i++)
		cin>>node[i].r;
	for(int i=1;i<=n-1;i++){
		scanf("%d%d",&l,&k);
		node[l].dad=k;
		if(node[k].fbranch==0){
			node[k].fbranch=node[k].lbranch=i;
			branch[i].child=l;
		}
		else{
			branch[node[k].lbranch].next=i;
			node[k].lbranch=i;
			branch[i].child=l;
		}
	}
	while(node[leader].dad) leader=node[leader].dad;
	return printf("%d",max_(happy(leader,0),happy(leader,1))),0;
}

  容易证明该算法的时间复杂度为 O ( n ) O(n) O(n),也算是比较快的算法了。

  • 22
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值