您的位置:首页 > 其它

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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: