zoukankan      html  css  js  c++  java
  • 【算法•日更•第十期】树型动态规划&区间动态规划:加分二叉树题解

      废话不多说,直接上题:


    1580:加分二叉树


    时间限制: 1000 ms         内存限制: 524288 KB
    提交数: 121     通过数: 91 

    【题目描述】

    原题来自:NOIP 2003

    设一个 n 个节点的二叉树 tree 的中序遍历为 (1,2,3,,n),其中数字 1,2,3,,n 为节点编号。每个节点都有一个分数(均为正整数),记第 i 个节点的分数为 di ,tree 及它的每个子树都有一个加分,任一棵子树 subtree(也包含 tree 本身)的加分计算方法如下:

    记 subtree 的左子树加分为 l,右子树加分为 r,subtree 的根的分数为 a,则 subtree 的加分为:

    l×r+a

    若某个子树为空,规定其加分为 1,叶子的加分就是叶节点本身的分数。不考虑它的空子树。

    试求一棵符合中序遍历为 (1,2,3,⋯,n) 且加分最高的二叉树 tree。

    要求输出:

    1、tree 的最高加分;

    2、tree 的前序遍历。

    【输入】

    第一行一个整数 n 表示节点个数;

    第二行 n 个空格隔开的整数,表示各节点的分数。

    【输出】

    第一行一个整数,为最高加分 b;

    第二行 n 个用空格隔开的整数,为该树的前序遍历。

    【输入样例】

    5
    5 7 1 2 10

    【输出样例】

    145
    3 1 2 4 5

    【提示】

    数据范围与提示:

    对于 100% 的数据,n<30,b<100,结果不超过 4×109 。

    【来源】


      这道题在信息奥赛一本通oj上被归类为树型动态规划,其实并不是,确实有树的数据结构要用到,但是更多需要的是区间动态规划的技巧。如果你不会区间动态规划,请戳这里学习区间动态规划

      首先,我们把这道题分成两个部分,分别是求最高加分和树的先序遍历,先来说最高加分怎么办:

      先思考一下,题目中给出的顺序(中序遍历)有什么用?中序遍历的顺序是左子树 -> 根节点 -> 右子树,那么我们不妨枚举根节点的位置,那么根节点左边的就是左子树,右边的就是右子树,然后继续在左右子树中以同样的方法枚举根节点……直到分成叶子结点,返回该节点的分数就可以了。这样是什么,这不就是递归吗?所以我们这里采用记忆化搜索的形式来写。

      那么我们就可以用f[i][j]来表示ij区间内的最大加分,状态转移方程就很明了了:f[i][j]=max{f[i][k-1]+f[k+1][j]+a[i]}

      不过需要注意一点,可能会出现一些越界(其实是空树)情况,例如i>j时,记得返回1。

      然后,我们来考虑怎么输出先序遍历,我们可以在执行上面的动态规划的过程中记录下一个root二维数组,root[i][j]表示i到j区间内选择的最优根节点编号,方便于输出先序遍历结果。最后只要按照先序遍历的顺序遍历就行了,碰到叶子节点时直接输出。

      好了,代码如下:

     1 #include<iostream>
     2 using namespace std;
     3 int f[1000][1000],a[1000],tree[1000],root[1000][1000],cnt,mid,n,ans;
     4 int dp(int i,int j)//动态规划 
     5 {
     6     if(i==j) return a[i];//碰到叶子结点直接返回 
     7     if(i>j) return 1;//注意空树(不存在左子树或右子树或都不存在)的情况 
     8     if(f[i][j]) return f[i][j];//记忆化 
     9     for(int k=i;k<=j;k++)
    10     {
    11         int x=dp(i,k-1)*dp(k+1,j)+a[k];//状态转移方程 
    12         if(f[i][j]<x)
    13         {
    14             f[i][j]=x;
    15             root[i][j]=k;//记录i~j区间的根节点 
    16         }
    17     }
    18     return f[i][j]; 
    19 }
    20 void print(int i,int j)
    21 {
    22     if(i>j) return;//避免空树 
    23     if(i==j)//叶节点直接输出 
    24     {
    25         cout<<i<<" ";
    26         return;
    27     }
    28     cout<<root[i][j]<<" ";//
    29     print(i,root[i][j]-1);//左子树 
    30     print(root[i][j]+1,j);//右子树 
    31 }
    32 int main()
    33 {
    34     cin>>n;
    35     for(int i=1;i<=n;i++)
    36     cin>>a[i];
    37     cout<<dp(1,n)<<endl;
    38     print(1,n);
    39     return 0;
    40 }
  • 相关阅读:
    P4556 [Vani有约会]雨天的尾巴
    [模拟赛20180809] 旅程
    【jzoj3464】秀姿势
    【noip2013】火柴排队
    做运动
    【noip2013】花匠
    【noip2016】愤怒的小鸟
    【bzoj4326】【noip2015】运输计划
    作业二:个人编程项目——编写一个能自动生成小学四则运算题目的程序
    自我介绍
  • 原文地址:https://www.cnblogs.com/TFLS-gzr/p/11180335.html
Copyright © 2011-2022 走看看