NuptOJ1039加分二叉树——树形动态规划
2014-06-07 22:02
183 查看
加分二叉树
时间限制(普通/Java):1000MS/3000MS 运行内存限制:65536KByte总提交:105 测试通过:33
描述
设一个n个节点的二叉树tree的中序遍历为(l,2,3,…,n),其中数字1,2,3,…,n为节点编号。每个节点都有一个分数(均为正整数),记第j个节点的分数为dj,tree及它的每个子树都有一个加分,任一棵子树subtree(也包含tree本身)的加分计算方法如下:
subtree的左子树的加分× subtree的右子树的加分+subtree的根的分数
若某个子树为空,规定其加分为1,叶子的加分就是叶节点本身的分数。不考虑它的空子树。
试求一棵符合中序遍历为(1,2,3,…,n)且加分最高的二叉树tree。要求输出;
(1)tree的最高加分
(2)tree的前序遍历
输入
第1行:一个整数n(n<30),为节点个数。
第2行:n个用空格隔开的整数,为每个节点的分数(分数<100)。
输出
第1行:一个整数,为最高加分(结果不会超过4,000,000,000)。
第2行:n个用空格隔开的整数,为该树的前序遍历。
样例输入
5
5 7 1 2 10
样例输出
145
3 1 2 4 5
题目来源
NOIP2003
分析:已吐 orz
连着2道DP,刚不住了~ T_T
非常精妙的树形DP。
3
/ \
1 4
\ \
2 5
设节点d为最优的根节点,那么可以把这棵树分成[1,d-1]和[d+1,n],这颗树的加分为子树[1,d-1]的加分与子树[d+1,n]加分的乘积与d的加分的和,而[1,d-1]和[d+1,n]的加分也可也一定是最优加分,所以这个题具有最优子结构。
那么可以用动态规划
设f[k,j]为子树k到j的最高加分,求f[k,j]的最优值,就要求f[1,d-1]和f[d+1,n]的最优加分,那么枚举根节点p,则有
f[k,j]的最优值=f[k,p-1]*f[p+1,j]+v[p](k<p<j) (v[p]为p节点的分数)
规划方程为f[k,j]=max{f[k,p-1]*f[p+1,j]+v[p]}(k<p<j)
但是,根据题目,左子树或右子树可以为空,即当k=p或p=j,于是对此特殊处理,求出这种情况下的加分,再进行比较
动态规划顺序:根据规划方程,要求f[i,j]要先求解出f[i,k-1]和f[k+1,j],那么可以根据j-k从小到大的顺序动态规划,其实就是要求一棵树的加分,先要求它的子树加分,根据树的大小作为规划顺序
打印前序遍历: 前序遍历的顺序为根->左子树->右子树 可以在程序中每一次更新当前子树的加分时,用tree[]记录此时的子树的根节点,那么最后纪录的就是最优的二叉树的节点 打印时,先找出f[1,n]的根节点p,打印,然后分成f[1,p-1]和f[p+1,n]递归打印,直到打印完叶子节点。
精妙的地方在于:由于是中序遍历,节点是相邻的,非常方便操作。
注意点:
①若某个子树为空,规定其加分为1,叶子的加分就是叶节点本身的分数。不考虑它的空子树。这句话要好好体会,这个就和上一个stone(最小代价生成树那道题)的动态规划不一样。那道题直接拉上来比较,且只要比较一次。而这道题由于当某个子树为空时,用程序的写法相当于乘以1。所以还是有区别的。
②输出格式有个坑。中序遍历输出中间用空格隔开且最后没有空格。其实还蛮有难度的。学习别人的代码,空格输出在前面,而不是跟在后面,这样最后一个元素不用跟着输出一个空格了。用了一个bool变量来判断是否为输出的第一个,这样只有第一个前面没有空格,其余元素前面都加一个空格。
写法有多种!DP思想还需体会!
方法一:沿用上一题最小生成树的风格!
#include<stdio.h> #include<string.h> //树形动态规划 int dp[30][30], tree[30][30]; bool isFirst; // 输出空格 void print(int l, int r) { if(l > r) return ; if(isFirst) isFirst = false; else printf(" "); printf("%d",tree[l][r]); print(l, tree[l][r]-1); print(tree[l][r]+1, r); } int main() { int n; while(scanf("%d",&n) != EOF) { memset(dp, 0 ,sizeof(dp)); memset(tree, 0, sizeof(tree)); for(int i=1;i<=n;i++) { scanf("%d",&dp[i][i]); tree[i][i] = i; } for(int i=1;i<=n-1;i++) { dp[i][i+1] = dp[i][i] + dp[i+1][i+1]; tree[i][i+1] = i; } for(int d=2;d<n;d++) { for(int i=1,j=i+d;j<=n;i++,j++) { 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]; tree[i][j] = k; } if(dp[i][j] < dp[i][j-1] + dp[j][j]) // 左子树为空 { dp[i][j] = dp[i][j-1] + dp[j][j]; tree[i][j] = j; } if(dp[i][j] < dp[i+1][j] + dp[i][i]) // 右子树为空 { dp[i][j] = dp[i+1][j] + dp[i][i]; tree[i][j] = i; } } } } printf("%d\n",dp[1] ); isFirst = true; print(1, n); } return 0; }
方法二:DP中递归
#include<iostream> #include<cstring> using namespace std; int n; long dp[40][40]; int tree[40][40]; bool isFirst; void visit(int i,int j) // 当中序遍历为1,2,3,4,5。。。时,前序遍历的求法。 { if(i>j) return ; if(isFirst) isFirst=false; else cout<<" "; cout<<tree[i][j]; visit(i,tree[i][j]-1); visit(tree[i][j]+1,j); } int Search_dp(int l,int r) { int i; int now; if(l>r) return 1; else { if(dp[l][r] == -1) { for(i=l;i<=r;i++) { now = Search_dp(l,i-1)*Search_dp(i+1,r)+dp[i][i]; if(now > dp[l][r]) { dp[l][r] = now; tree[l][r] = i; } } } return dp[l][r]; } } int main() { while(cin>>n) { memset(dp,-1,sizeof(dp)); memset(tree,-1,sizeof(tree)); for(int i=1;i<=n;i++) { cin>>dp[i][i]; tree[i][i] = i; } cout<<Search_dp(1,n)<<endl; isFirst = true; visit(1,n); cout<<endl; } return 0; }
相关文章推荐
- 洛谷 P1040 加分二叉树
- 加分二叉树
- Code[VS] 1090 加分二叉树
- [NOIP2003]区间dp-加分二叉树
- P1073 加分二叉树
- NOIP2013加分二叉树
- luogu1040加分二叉树
- Noip 2003T3 加分二叉树
- 洛谷1040加分二叉树【NOIP2003普及组】
- Codevs1090 加分二叉树
- 加分二叉树
- 加分二叉树
- sjtu 1077 加分二叉树
- 洛谷 P1040 [NOIP2003 T3] 加分二叉树
- 加分二叉树_洛谷1040_dp
- 加分二叉树
- 加分二叉树
- 加分二叉树[NOIP 2003提高组][Codevs 1090]
- P1040 加分二叉树
- NOIP 2003 提高组 复赛 加分二叉树