一本通-加分二叉树+分离与合体(区间DP+记录方案)

简介: 一本通-加分二叉树+分离与合体(区间DP+记录方案)

加分二叉树原题链接

题目描述

设一个n个节点的二叉树tree的中序遍历为(1,2,3,…,n),其中数字1,2,3,…,n为节点编号。


每个节点都有一个分数(均为正整数),记第i个节点的分数为di,tree及它的每个子树都有一个加分,任一棵子树subtree(也包含tree本身)的加分计算方法如下:


subtree的左子树的加分 × subtree的右子树的加分 + subtree的根的分数


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


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


要求输出:


(1)tree的最高加分


(2)tree的前序遍历


输入格式

第1行:一个整数n,为节点个数。


第2行:n个用空格隔开的整数,为每个节点的分数(0<分数<100)。


输出格式

第1行:一个整数,为最高加分(结果不会超过4,000,000,000)。


第2行:n个用空格隔开的整数,为该树的前序遍历。如果存在多种方案,则输出字典序最小的方案。


数据范围

n<30

输入样例:

5

5 7 1 2 10

输出样例:

145

3 1 2 4 5

思路:

其实我不咋会这道题(小声bb)

先来看一下什么是树的前序遍历等 传送门

先来解决第一个问题,如何计算树的最大加分。

我们设dp[i] [j]表示所有中序遍历是[i,j]这一段的加分最大的树,可以枚举根节点所在位置进行状态转移

  for(int k=l;k<r;k++){
         int left=1,right=1;
         if(k!=l) left=dp[l][k-1];///存在左子树
         if(r!=k) right=dp[k+1][r];//存在右子树
         dp[l][r]=max(dp[l][r],left*right+a[k]);
    }

最后的最大加分就是dp[1][n]

再来看一下如何输出方案~

我们记pre[i][j]表示使得[i,j]取得最大的加分的根节点的值。又因为前序遍历的输出是先输出根节点,再输出左右子树,我们就可以利用这一特性,进行方案的输出。

代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn=35;
int a[maxn];
int dp[maxn][maxn];///表示中序遍历是i~j的树的加分的最大值
int pre[maxn][maxn];//pre[i][j]表示[i,j]的加分最大值对应的的根节点的编号
int n;
void dfs(int l,int r){
    if(l>r) return ;
    int x=pre[l][r];//输出根节点
    cout<<x<<" ";
    dfs(l,x-1);//输出左子树
    dfs(x+1,r);///输出右子树
}
int main(){
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int len=1;len<=n;len++)
        for(int l=1;l+len-1<=n;l++){
            int r=l+len-1;
            if(l==r) dp[l][r]=a[l],pre[l][r]=l;//叶节点
            else{
                for(int k=l;k<r;k++){
                    int left=1,right=1;
                    if(k!=l) left=dp[l][k-1];///存在左子树
                    if(r!=k) right=dp[k+1][r];//存在右子树
                   //dp[l][r]=max(dp[l][r],left*right+a[k]);
                   if(dp[l][r]<left*right+a[k]){
                       dp[l][r]=left*right+a[k];
                       pre[l][r]=k;
                   }
                }
            }
        }
    cout<<dp[1][n]<<endl;
    dfs(1,n);
    return 0;
}

类似的题:

分离与合体 原题链接

经过在机房里数日的切磋,LYD从杜神牛那里学会了分离与合体,出关前,杜神牛给了他一个测试……

杜神牛造了n个区域,他们紧邻着排成一行,编号1…n。在每个区域里都放着一把OI界的金钥匙,每一把都有一定的价值,LYD当然想得到他们了。然而杜神牛规定LYD不能一下子把他们全部拿走,而是每次只可以拿一把。为了尽快得到所有金钥匙,LYD自然就用上了刚学的分离与合体特技。

一开始LYD可以选择1…n-1中的任何一个区域进入,我们不妨把这个区域记为k。进入后LYD会在k区域发生分离,从而分离成两个小LYD。分离完成的同时会有一面墙在k区域和k+1区域间升起,从而把1…k和k+1…n阻断成两个独立的区间,并在各自区间内任选除区间末尾之外(即从1…k-1和k+1…n-1中选取)的任意一个区域再次发生分离,这样就有了四个小小LYD……重复以上所叙述的分离,直到每个小LYD发现自己所在的区间只剩下了一个区域,那么他们就可以抱起自己梦寐以求的OI金钥匙。

但是LYD不能就分成这么多个个体存在于世界上,这些小LYD还会再合体,合体的小LYD所在区间中间的墙会消失。合体会获得(合并后所在区间左右端区域里金钥匙价值之和) \times()×(之前分离的时候所在区域的金钥匙价值)。

例如,LYD曾在1…3区间中的2号区域分离成为1…2和3…3两个区间,合并时获得的价值就是(1号金钥匙价值+3号金钥匙价值) \times()×(2号金钥匙价值)。

LYD请你编程求出最终可以获得的最大总价值,并按照分离阶段从前到后,区域从左到右的顺序,输出发生分离区域编号。若有多种方案,选择分离区域尽量靠左的方案(也可以理解为输出字典序最小的)。

例如先打印一分为二的区域,然后从左到右打印二分为四的分离区域,然后是四分为八的……

输入描述:

第一行一个正整数n第二行n个用空格分开的正整数a_ia

i

,表示1…n区域里每把金钥匙的价值。

输出描述:

第一行一个数,表示获得的最大价值

第二行按照分离阶段从前到后,区域从左到右的顺序,输出发生分离区域编号。若有多种方案,选择分离区域尽量靠左的方案(也可以理解为输出字典序最小的)。

示例1

输入

复制

7

1 2 3 4 5 6 7

输出

复制

238

1 2 3 4 5 6

备注:

对于20 %20%的数据,n \leq 10n≤10;

对于40 %40%的数据,n \leq 50n≤50;

对于100 %100%的数据,n,a_i \leq 300n,a

i

≤300,保证运算过程和结果不超过32位正整数范围。


题意:(题面好长好烦)

跟上面大体类似的,只是加分的方式改变了:


LYD曾在1…3区间中的2号区域分离成为1…2和3…3两个区间,合并时获得的价值就是(1号金钥匙价值+3号金钥匙价值) \times()×(2号金钥匙价值)。


代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn=1100;
int a[maxn];
int dp[maxn][maxn];
int pre[maxn][maxn];
int n,k,t;
void dfs(int u,int l,int r){
    if(l>=r) return ;
    int x=pre[l][r];
    if(u==k){
        cout<<x<<" ";
        t=1;
        return ;
    }
    dfs(u+1,l,x);dfs(u+1,x+1,r);
}
int main(){
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int len=1;len<n;len++)
        for(int l=1;l+len<=n;l++){
            int r=l+len;
            for(int k=l;k<r;k++){
                int left=0,right=0;
                left=dp[l][k];
                right=dp[k+1][r];
                if(dp[l][r]<(left+right)+(a[l]+a[r])*a[k]){
                    dp[l][r]=(left+right)+(a[l]+a[r])*a[k];
                    pre[l][r]=k;
                }
            }
        }
    cout<<dp[1][n]<<endl;
    t=1;
    while(t){
        t=0;
        k++;
        dfs(1,1,n);
    }
    return 0;
}

参考文献:传送门


目录
相关文章
|
4天前
|
存储 缓存 安全
几道 C/C 题涉及的知识盲区
几道 C/C 题涉及的知识盲区
【期末不挂科-单片机考前速过系列P6】(第六章:10题速过定时计数器的结构和工作方式例题)经典例题盘点(带图解析)
【期末不挂科-单片机考前速过系列P6】(第六章:10题速过定时计数器的结构和工作方式例题)经典例题盘点(带图解析)
【期末不挂科-单片机考前速过系列P7】(第七章:11题速过串行口基本概念/结构/工作方式/双机通信例题)经典例题盘点(带图解析)
【期末不挂科-单片机考前速过系列P7】(第七章:11题速过串行口基本概念/结构/工作方式/双机通信例题)经典例题盘点(带图解析)
|
4月前
|
存储 定位技术
【天梯赛】L2-048 寻宝图 (DFS做法)
遇到一个非'0'字符(也就是'1'和 宝藏'2'到'9')就让ans++,同时将这个非'0'字符染色为'0',然后往四个方向(上、下、左、右)搜索,这里的目的是那一片岛屿(也就是那一片为'1'的部分)都染色为‘0’。本题就请你统计一下,给定的地图上一共有多少岛屿,其中有多少是有宝藏的岛屿。为了判断有宝藏的岛屿,这里我开了一个全局变量f来判断这一片岛屿是否有宝藏(也就是有无字符'2'-'9'),当搜到字符'2'~'9'时就将f标记为1。在一行中输出 2 个整数,分别是岛屿的总数量和有宝藏的岛屿的数量。
80 5
用试题这把“剑“帮你破除指针与数组之间的那些猫腻
用试题这把“剑“帮你破除指针与数组之间的那些猫腻
63 0
|
存储 Linux
还在担心期末挂科吗? 期末必备复习资料-----“树“的概念
还在担心期末挂科吗? 期末必备复习资料-----“树“的概念
134 0
|
存储 编译器
IAT表入门简析【滴水逆向三期52笔记】
IAT表入门简析【滴水逆向三期52笔记】
树的储存形势&代码实现(跑路人笔记1)
树的储存形势&代码实现(跑路人笔记)
树的储存形势&代码实现(跑路人笔记1)