中序遍历固定为 1..n 时子树必是连续区间,区间 DP 枚举根求最高加分,并记录每段最优根还原前序遍历。
OJ: roj
题目 ID: 3526
难度:普及+/提高-
标签:动态规划区间DP二叉树python
创建: 2026-10-02 05:16
更新: 2026-10-10 11:30
形式化题目
给定
其中空子树的加分规定为
求:最高加分,以及取到该加分的那棵树的前序遍历。
例如
graph TD
n3["3:12×12+1=145"] -->|左| n1["1:1×7+5=12"]
n3 -->|右| n4["4:1×10+2=12"]
n1 -->|右| n2["2:叶子 7"]
n4 -->|右| n5["5:叶子 10"]
观察这棵树:中序恰好是
正解
思路
朴素想法与瓶颈。 最直接的做法是枚举所有中序为
观察一:加分公式左右独立。 乘法两侧的子树只以「加分标量」的身份相乘,左子树怎么长完全不影响右子树的最优性。所以以
观察二:中序固定 ⇒ 子树是连续区间。 中序遍历为
状态与转移。 设
边界:空区间
样例的 DP 表。 下表是样例 加分/根(即
| 1 | 2 | 3 | 4 | 5 | |
|---|---|---|---|---|---|
| 1 | 5/1 |
12/1 |
13/1 |
25/3 |
145/3 |
| 2 | 7/2 |
8/2 |
15/3 |
85/3 |
|
| 3 | 1/3 |
3/3 |
13/3 |
||
| 4 | 2/4 |
12/4 |
|||
| 5 | 10/5 |
观察三点:对角线是叶子,直接等于 145/3 给出最终答案——根是 3,左段
前序遍历还原。 算分时同时记录每段的最优根 3 1 2 4 5。
最终答案在 best(l, r) 的返回值 (最高加分, 最优根) 正是 solve() 只负责读入、调用和输出。
代码
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-10-10 11:30
* update_at: 2026-10-10 11:30
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n;
ll d[35];
ll score[35][35]; // 区间最高加分
int root[35][35]; // 取到最高加分的根
// best(l, r):返回 (最高加分, 根)
ll best(int l, int r) {
if (l > r) return 1;
if (l == r) { root[l][r] = l; return d[l]; }
if (score[l][r] != -1) return score[l][r];
ll top = 0; int top_k = l;
for (int k = l; k <= r; k++) {
ll s = best(l, k - 1) * best(k + 1, r) + d[k];
if (s > top) { top = s; top_k = k; }
}
score[l][r] = top;
root[l][r] = top_k;
return top;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
for (int i = 1; i <= n; i++) cin >> d[i];
for (int i = 0; i <= n; i++)
for (int j = 0; j <= n; j++)
score[i][j] = -1;
cout << best(1, n) << "\n";
// 前序遍历:根 -> 左 -> 右
vector<int> order;
vector<pair<int, int> > stack;
stack.push_back(make_pair(1, n));
while (!stack.empty()) {
int l = stack.back().first, r = stack.back().second;
stack.pop_back();
if (l <= r) {
int k = root[l][r];
order.push_back(k);
stack.push_back(make_pair(k + 1, r));
stack.push_back(make_pair(l, k - 1));
}
}
for (size_t i = 0; i < order.size(); i++) {
if (i) cout << " ";
cout << order[i];
}
cout << "\n";
return 0;
}#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-10-02 05:17
# update_at: 2026-10-02 05:17
import sys
from functools import cache
@cache
def best(l: int, r: int, d: tuple[int, ...]) -> tuple[int, int]:
"""中序为 l..r 的子树的 (最高加分, 取到最高的根);空区间加分为 1、根记 0。
根 k 的加分 = 左子树加分 × 右子树加分 + d[k],两棵子树各自独立地也取最高,
故按区间长度递推;叶子的加分就是它本身的分数(不套空子树规则),
同分时取最靠左的根,保证前序遍历与标准答案一致。
"""
if l > r:
return (1, 0)
if l == r:
return (d[l], l)
top, top_k = 0, l
for k in range(l, r + 1):
left = best(l, k - 1, d)[0]
right = best(k + 1, r, d)[0]
score = left * right + d[k]
if score > top:
top, top_k = score, k
return (top, top_k)
def solve() -> None:
data = iter(map(int, sys.stdin.buffer.read().split()))
n = next(data)
d: tuple[int, ...] = (0,) + tuple(next(data) for _ in range(n)) # 下标 1..n 的分数
top_score, _ = best(1, n, d)
# 前序遍历:根 → 左子树 → 右子树;用栈把"先压右再压左"倒成正确的出栈顺序
order: list[int] = []
stack = [(1, n)]
while stack:
l, r = stack.pop()
if l <= r:
k = best(l, r, d)[1]
order.append(k)
stack.append((k + 1, r))
stack.append((l, k - 1))
print(top_score)
print(*order)
if __name__ == "__main__":
solve()复杂度
- 时间复杂度:
。区间数 ,每段枚举根 ;记忆化保证每个区间只算一次。 - 空间复杂度:
。缓存所有区间的 (加分, 根),外加的前序栈。 时实测单点约 0.015 s、峰值内存约 15 MB,相对 1000 ms / 32 MB 的限制非常宽裕。
总结
这道题的关键是两次视角转换:把「枚举树形态」换成「枚举区间与根」——因为中序遍历固定为
两个容易踩的点:一是叶子边界,题面规定叶子加分就是