✪ 树形dp ✪

没有上司的舞会

P1352 没有上司的舞会 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

此题画出来是一种这样的结构(有一种“前向星存图”的方法,但这题没必要用)

同样可以采取dp的思考方式

dp[n][2] 数组代表每个人做出决策之后的快乐值,0是不去,1是去

1.初始化

dp[x][0]=0;

dp[x][1]=r[x];

2.状态转移方程

如果x是当前职工,y是他的下属,那么:

dp[x][0]+=max(dp[y][0],dp[y][1]); //上司没去,下属要么去,要么不去,那就取一个max

dp[x][1]+=dp[y][0];        //上司去了下属不去

那么,我们如何获取所有下属的每一种情况呢——树形结构——遍历+递归!

void dfs(int x){
	//树形dp常常要用递归,自底向上
	dp[x][0]=0;	//不参加,带来的快乐值贡献为0
	dp[x][1]=r[x]; //参加
	for(int i=0;i<son[x].size();i++){
		//遍历ta的所有下属
		int y=son[x][i];	//y是当前遍历到的下属的编号
		dfs(y);
		dp[x][0]+=max(dp[y][0],dp[y][1]);
		dp[x][1]+=dp[y][0]; 
	} 
} 

在遍历中就更新了dp数组~~~

那么,把谁作为参数传进去呢?

显然应该传“树根”——树根就是没有上司的那一个人(而且题目中说了这是树,不会有图那种情况)

所以再额外设定一个v数组,来记录每一个员工有没有上司

当我们记忆化搜索了所有的情况,dp数组也都有了值,现在就要获取答案了

很简单,树根收集了所有子树的情况:

    cout<<max(dp[root][0],dp[root][1])<<endl;

至此完成

完整代码:

#include<iostream>
#include<vector>
using namespace std;

#define MAXN 6005
int r[MAXN];		//快乐指数 
int v[MAXN];
vector<int> son[MAXN];	//每个人都存一个自己的下属数组
int dp[MAXN][2]; //dp[i]代表第i个人,0不去,1去

void dfs(int x){
	//树形dp常常要用递归,自底向上
	dp[x][0]=0;	//不参加,带来的快乐值贡献为0
	dp[x][1]=r[x]; //参加
	for(int i=0;i<son[x].size();i++){
		//遍历ta的所有下属
		int y=son[x][i];	//y是当前遍历到的下属的编号
		dfs(y);
		dp[x][0]+=max(dp[y][0],dp[y][1]);
		dp[x][1]+=dp[y][0]; 
	} 
} 

int main(){
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)
		cin>>r[i];
	for(int i=1;i<n;i++){
		int x,y;
		cin>>x>>y;
		son[y].push_back(x);
		v[x]=1;	//代表ta有上司 
	}
	int root;
	for(int i=1;i<=n;i++){
		if(!v[i]){
			root=i;
			break;	//找到没有上司的那个人 
		}
	} 
	dfs(root);
	cout<<max(dp[root][0],dp[root][1])<<endl;
    return 0;
}

加分二叉树【题解是区间dp!记得改..】

P1040 [NOIP2003 提高组] 加分二叉树 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

 暂时无法参透其中的奥秘.....

#include<iostream>
using namespace std;

typedef long long ll;
ll n;
//dp[i][j]代表结点i到结点j构成的子树的最大分数 
//root[i][j]记录dp[i][j]最大时的根编号 
ll dp[50][50],root[50][50];

//前序遍历
void print(ll l,ll r){
	if(l>r) return;
	cout<<root[l][r]<<" ";		//根 
	if(l==r)return;	
	print(l,root[l][r]-1);	//左 
	print(root[l][r]+1,r);    //右 
} 

int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>dp[i][i];
		dp[i][i-1]=1;  //空子树分数为1 
		root[i][i]=i;
	} 
	for(int len=1;len<n;len++){
		//枚举长度 
		for(int i=1;i+len<=n;i++){
			//枚举起始位置 
			int j=i+len;
			dp[i][j]=dp[i+1][j]+dp[i][i];  //默认左子树为null
			root[i][j]=i;	//默认从起点选根
			
			for(int k=i+1;k<j;k++){
				if(dp[i][j]<dp[i][k-1]*dp[k+1][j]+dp[k][k]){
					dp[i][j]=dp[i][k-1]*dp[k+1][j]+dp[k][k];
					root[i][j]=k;
				}
			} 
		} 
	} 
	cout<<dp[1][n]<<endl;
	print(1,n);
}

【树形dp的解法】

#include<iostream>
using namespace std;

//设dp[i][j]为i~j组成子树的最大分值
//root[i][j]记录取得最大分值时的根编号  i==j时,root[i][j]=i 
//初始化为-1(这表明最大分值尚未计算出) 

typedef long long ll;
int n;
ll dp[50][50];
ll root[50][50];
bool first;

ll dfs(int L,int R){
	ll ans;		
	if(L>R)return 1;
	if(dp[L][R]==-1){
		//若尚未计算出最大分值
		for(int k=L;k<=R;k++){
			//枚举每一个K 
			ans=dfs(L,k-1)*dfs(k+1,R)+dp[k][k]; 
			if(ans>dp[L][R]){
				dp[L][R]=ans;
				root[L][R]=k;
			}
		} 
	}
	return dp[L][R];	//L~R的最高分值 
}

//输出前序遍历
void print(int L,int R){
	if(L>R)return;
	if(first) first=false;
	else cout<<" ";		//保证第一个顶点前没有空格 
	cout<<root[L][R];
	print(L,root[L][R]-1);
	print(root[L][R]+1,R);
} 

int main(){

	cin>>n;
	for(int i=1;i<=n;i++){
		for(int j=i;j<=n;j++)
			dp[i][j]=-1;
	}
	
	for(int i=1;i<=n;i++){
		cin>>dp[i][i];
		root[i][i]=i;
	}
	cout<<dfs(1,n)<<endl;
	first=true;
	print(1,n);
} 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值