首页 > 其他分享 >洛谷P1040. 加分二叉树

洛谷P1040. 加分二叉树

时间:2023-01-11 23:33:46浏览次数:49  
标签:洛谷 int tree P1040 二叉树 print return 节点

题目描述

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

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

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

若某个子树为空,规定其加分为 \(1\)。

叶子的加分就是叶节点本身的分数,不考虑它的空子树。

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

要求输出:

(1)tree的最高加分

(2)tree的前序遍历

输入格式

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

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

输出格式

第 \(1\) 行:一个整数,为最高加分(结果不会超过int范围)。

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

解题思路

\(\qquad\)这题初看没有入手点,但是题目有一个特殊的条件:中序遍历是\(1\sim n\)的升序,这也就代表我们可以抽象一下,将这棵树压扁,就可以到一个数轴上,是一个完整的区间\([1,n]\),所以我们可以考虑一下区间\(DP\)。

状态表示

\[用f(l,r)表示数轴上区间[l,r]的最大加分 \]

状态计算

\(\qquad\)一个区间的切割方法就是:对于区间\(f[l,r]\),我们在这个子树上假设它的根节点是\(k\),那么在\(k\)左边的区间都是以\(k\)为根的二叉树的左子,右边区间同理。所以对于以\(k\)为根的树,左子为\([l,k-1]\),右子为\([k+1,r]\),根据题目的加分规则,那$$\large score_{tree} = score_{left}\times score_{right} + w_{root}$$

\(\qquad\)此外还要注意,因为二叉树允许没有左子或者没有右子,因此\(k\)应该在\([l,r]\)而非\([l+1,r-1]\)枚举,并且对于\(l=k\)的情况,\(score_{left}=1\),右子同理。

状态统计

\(\qquad\)由于题目需要输出二叉树的前序遍历,所以我们在\(DP\)的时候要保存一些信息。

补充:二叉树的前序遍历,先输出根,再递归左子,再递归右子。

\(\qquad\)对于这个最大方案的根,我们可以在\(DP\)的过程中顺便保存让\([l,r]\)区间加分最大的根节点\(g[l,r]\),为了让字典序最小,我们让断点\(k\)从左向右扫描,遇到更大的值就要\(f[]和g[]\)一起更新,当值相同的时候,以\(k\)越小越好。

然后在输出的时候递归处理:

void print(int l, int r) 
{
    if (l > r) return ; // 不构成节点
    int k = g[l][r]; // 根节点 
    printf("%d ", k);
    print(l, k - 1), print(k + 1, r); // 分别递归左右子
}

代码

会贴两份,递推\(DP\)和记忆化搜索

递推DP

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 50;
int f[N][N], g[N][N], n, w[N];

void print(int l, int r) 
{
    if (l > r) return ; // 不构成节点
    int k = g[l][r]; // 根节点 
    printf("%d ", k);
    print(l, k - 1), print(k + 1, r); // 分别递归左右子
}

int main() 
{
    scanf("%d", &n);
    
    for (int i = 1; i <= n; i ++ ) scanf("%d", &w[i]);
    
    memset(f, 0xcf, sizeof f);
    for (int i = 1; i <= n; i ++ ) 
        g[i][i] = i, f[i][i] = w[i]; // 长度为1的叶子节点分数和根的初始化
    
    for (int len = 2; len <= n; len ++ ) //长度为1的初始化过了,从2开始
    {
        for (int l = 1, r = l + len - 1; r <= n; l ++, r ++ ) //枚举左端点和右端点
        {
            for (int k = l; k <= r; k ++ ) // 枚举断点
            {
                int &v = f[l][r], u, L, R;
                L = (k == l) ? 1 : f[l][k - 1]; // 如果k == l代表没有左子树,左分数为1
                R = (k == r) ? 1 : f[k + 1][r]; // 如果k == r代表没有右子树
                u = L * R + w[k]; // 分数计算
                if (u > v) v = u, g[l][r] = k; // 更新信息
            }
        }
    }
    
    printf("%d\n", f[1][n]);
    print(1, n);
    
    return 0;
}

记忆化搜索

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 50;
int w[N], f[N][N], g[N][N], n;

int dp(int l, int r) 
{
    int &v = f[l][r];
    if (~v) return v; // 算过的直接调用
    if (l == r) return g[l][r] = l, f[l][r] = w[l]; // 叶子节点的处理
    
    for (int k = l; k <= r; k ++ ) 
    {
        int u, L, R;
        L = (k == l) ? 1 : dp(l, k - 1); // 如果k == l代表没有左子树,左分数为1
        R = (k == r) ? 1 : dp(k + 1, r); // 右子同理
        u = L * R + w[k];
        if (u > v) g[l][r] = k, v = u;
    }
    
    return v;
}

void print(int l, int r) 
{
    if (l > r) return ;
    int k = g[l][r];
    printf("%d ", k);
    print(l, k - 1), print(k + 1, r);
}

int main() 
{
    scanf("%d", &n);
    
    for (int i = 1; i <= n; i ++ ) scanf("%d", &w[i]);
    
    memset(f, -1, sizeof f);
    printf("%d\n", dp(1, n));
    print(1, n);
    
    return 0;
}

标签:洛谷,int,tree,P1040,二叉树,print,return,节点
From: https://www.cnblogs.com/StkOvflow/p/17045197.html

相关文章

  • 洛谷P6599 「EZEC-2」异或【题解】
    题目大意有\(T\)组数据,每组数据给定两个\(l,n\in\mathbb{N*}\),构造一个长为\(l\),每个元素不超过\(n\)的数组令他为\(a\),要使\[\sum_{i=1}^l\sum_{j=1}^{i-1}a_i\oplu......
  • CQOI2007,洛谷P4710涂色
    题目描述假设你有一条长度为\(5\)的木版,初始时没有涂过任何颜色。你希望把它的\(5\)个单位长度分别涂上红、绿、蓝、绿、红色,用一个长度为\(5\)的字符串表示这个目......
  • 代码随想录 Day15 LeetCode102. 二叉树的层序遍历
    102.二叉树的层序遍历classSolution{public:vector<vector<int>>levelOrder(TreeNode*root){vector<vector<int>>result;queue<TreeNode*......
  • 【BFS】LeetCode 103. 二叉树的锯齿形层序遍历
    题目链接103.二叉树的锯齿形层序遍历思路1额外加一个栈来使得访问节点的顺序是逆序的代码1classSolution{publicList<List<Integer>>zigzagLevelOrder(Tree......
  • 输出二叉树的右视图
    题目要求题目链接思路分析方法一:刚开始做的时候没有什么思路,就采用了最笨的方法根据中序和先序求出二叉树得到层序遍历的结果得到每一层的最后一个元素方法比较笨......
  • 543. 二叉树的直径
    题目给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。注意:两结点之间的路径长度......
  • 算法刷题 Day 14 | 二叉树的递归遍历
    今日内容:理论基础递归遍历迭代遍历统一迭代详细布置理论基础需要了解二叉树的种类,存储方式,遍历方式以及二叉树的定义文章讲解:https://programm......
  • 重建二叉树
    题目描述思路分析在中序遍历列表中找到先序遍历列表中第一个节点,以此为界限可以将二叉树分为左右子树,可以得知左子树和右子树的长度,在先序遍历列表中划分出来。再依次拿......
  • 二叉树
    1.二叉树的概念二叉树是n个有限元素的集合,该集合或为空、或由一个根节点及两个不相交的、被分别称为左子树和右子树的二叉树组成,是有序树。当集合为空时,称该二叉树为......
  • 代码随想录day14 LeetCode 144.二叉树的前序遍历 145.二叉树的后序遍历 94.二叉树的中
    144.二叉树的前序遍历classSolution{public:vector<int>v;vector<int>preorderTraversal(TreeNode*root){if(root==NULL)returnv;......