加分二叉树型
枚举根·区间即子树
本课摘要
加分二叉树型课程回答“枚举区间根节点时,子区间如何对应左右子树”。内容以枚举根·区间即子树为主线,配合逐步推导、可编辑演示、例题与练习形成可复查的学习闭环。
- 判断加分二叉树型的适用条件与状态边界
- 围绕“枚举根·区间即子树”推导转移与计算顺序
- 用演示、复杂度分析和配套题目校验实现
本课目录 · 0 节
正在整理目录…
加分二叉树:中序固定,怎么建最划算
换一个和石子合并神似、却长着「树」外壳的问题。给 5 个节点,分数依次是 。 要把它们建成一棵二叉树,唯一约束是:中序遍历必须恰好是 (即节点编号 = 它在中序里的位置)。一棵子树的加分这样算——记左子树加分 、右子树加分 、根分数 :
并约定空子树的加分为 (乘法里的单位元,不改变乘积)。不同的建树方式,总加分不同——问整棵树能拿到的最大加分,还要输出这棵最优树的前序遍历。
为什么这题绕不开区间?关键在中序被钉死了。二叉树的中序是「左 → 根 → 右」,所以一旦某个节点 当了根,中序里排在它前面的节点必然全在左子树、排在它后面的全在右子树——绝不会交叉。于是「以 为根」就把连续的一段编号 干净地劈成两段 与 ,各自又是一棵更小的子树。
要枚举所有合法二叉树?其数量是卡特兰数,随 指数爆炸,不可行。但上面那句「一段连续区间恰好是一棵子树」已经把出路点明——这正是区间 DP 的入口,和石子合并同一个模子。
状态与转移:枚举根,左右子树相乘
定状态。设 表示:把中序编号 到 这段连续区间建成一棵子树能拿到的最大加分。 要把 建成子树,必须先钦定它的根——设根是 (),则左子树是区间 、右子树是 ,两者都是更短的、已解的子区间。
按加分定义,以 为根时这棵子树的加分是 。哪个根最好?把每个 都试一遍,取最大:
这里 的空区间(当 时左子树 就是空、 时右子树为空)约定 :空子树没有节点,乘上 不改变乘积。 边界:(单节点自成一棵子树)。答案:。
和石子合并一样, 依赖的都是更短的子区间,所以递推必须按区间长度由短到长——短子树先算好,长区间枚举根时才有得引用。
本质:区间就是子树,分割点就是根
区间 DP 的同一套骨架,在这里换了层皮:石子合并枚举分割点把区间拆成左右两段相加;加分二叉树枚举根把区间拆成左右两棵子树相乘再加根分。一句话对上号——「一段连续区间 ⇔ 一棵子树;区间里选的那个分割点/根 ⇔ 子树的根」。认出这层对应,就把「建树」这件看似要枚举卡特兰数棵树的事,压成了 张三角表格、每格 枚举根。
跟着算一遍
用开头的例子(分数 ,中序编号 )走几步,重点盯住长度由短到长、以及空子树记 1:
看三角表一层一层长出来 · 枚举根、左右相乘
- 来源参与当前转移的依赖状态
- 当前正在计算或观察的状态
- 确定 / 最优已进入当前最优解或确定结果
- 已处理已经扫描、当前不再活跃的状态
- 非法越界、冲突或不可达状态
表是个上三角(只有 才是合法区间)。填表外层枚举长度 、内层枚举左端点 、最内枚举根 ——三层循环、外层是长度,和几乎所有区间 DP 一个骨架。约 个区间、每个枚举 个根,总复杂度 。加分二叉树的 ,轻松通过。中文伪代码:
for 长度 len = 2 … n: // ★外层枚举区间长度,由短到长
for 左端点 i = 1 … n-len+1:
j = i + len - 1
for 根 k = i … j: // 枚举区间 [i,j] 的根
左 = (k>i) ? dp[i][k-1] : 1 // 空子树记 1
右 = (k<j) ? dp[k+1][j] : 1
若 左*右 + score[k] 更大:
dp[i][j] = 左*右 + score[k]
root[i][j] = k // 记下根,供前序回溯深化:从 root 表前序回溯出整棵树
光有最大加分还不够,本题第二问要输出最优树的前序遍历。诀窍是在填表时顺手记下每个区间选的根:转移取到最优的那个 ,存进 。等整张表填完,从整区间 出发递归就能把树还原——先输出根、再递归左子树、再递归右子树:
先输出根 、再递归左子树 、再递归右子树 ——这正是前序遍历(根 → 左 → 右)的定义。空区间 直接返回、什么都不输出。 这套「记录决策 + 回溯还原方案」和石子合并枚举分割点是同一路数:石子那里若要还原合并顺序,也照样存 最优分割点、再递归左右段。区别仅在——加分树记的是根、还原出的是树结构;石子记的是分割点、还原出的是合并树。
建树演示:区间 ⇔ 子树
两个常见坑:空子树的 1,和相等时记哪个根
其一,空子树加分是 不是 ——它进的是乘法,记 0 会把整棵子树的加分抹成 0。写代码时用「越界即取 1」处理端点根( 或 )。 其二,本题前序遍历不唯一时要求字典序最小:枚举 从小到大、且转移用严格大于 才更新,就能让相等时保留更小的根——更小的根当前序第一个,字典序自然更小。
例题
#include <iostream>
using namespace std;
const int N = 35;
int n;
long long score[N]; // 每个节点的分数(按中序 1..n 排)
long long dp[N][N]; // dp[i][j] = 区间[i,j]建成一棵子树的最大加分
int root[N][N]; // root[i][j] = 取到最优时选的根,供前序回溯
// 前序遍历输出最优树:根 → 左子树 → 右子树
void preorder(int i, int j)
{
if (i > j) return; // 空子树,什么都不输出
int k = root[i][j]; // 这段区间的最优根
cout << k << " ";
preorder(i, k - 1); // 左子树 = 区间左半
preorder(k + 1, j); // 右子树 = 区间右半
}
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
cin >> score[i];
for (int i = 1; i <= n; i++) // 区间长度 1:单节点自成子树
{
dp[i][i] = score[i];
root[i][i] = i;
}
for (int len = 2; len <= n; len++) // ★外层枚举区间长度,由短到长
for (int i = 1; i + len - 1 <= n; i++)
{
int j = i + len - 1;
for (int k = i; k <= j; k++) // 枚举根 k
{
// 空子树(k 在端点)的加分记 1:越界即视作 1
long long lft = (k - 1 >= i) ? dp[i][k - 1] : 1;
long long rgt = (k + 1 <= j) ? dp[k + 1][j] : 1;
long long cur = lft * rgt + score[k];
if (cur > dp[i][j]) // 取最大,并记下根(相等取更小 k:不覆盖即最小)
{
dp[i][j] = cur;
root[i][j] = k;
}
}
}
cout << dp[1][n] << endl; // 第一问:最大加分
preorder(1, n); // 第二问:最优树的前序遍历
cout << endl;
return 0;
}#include <iostream>
using namespace std;
const int INF = 0x3f3f3f3f;
int n;
int a[205]; // 断环为链:复制一倍成 2n
int pre[205]; // 前缀和,sum(l..r) = pre[r] - pre[l-1]
int f[205][205]; // 最小合并代价
int g[205][205]; // 最大合并代价
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
a[i + n] = a[i]; // 复制一倍
}
for (int i = 1; i <= 2 * n; i++)
pre[i] = pre[i - 1] + a[i];
for (int len = 2; len <= n; len++) // 长度只到 n(一圈)
for (int l = 1; l + len - 1 <= 2 * n; l++)
{
int r = l + len - 1;
int s = pre[r] - pre[l - 1]; // 本区间合并代价 = 区间和
f[l][r] = INF;
g[l][r] = -INF;
for (int k = l; k <= r - 1; k++) // ★枚举分割点 k(对照:加分树枚举根)
{
f[l][r] = min(f[l][r], f[l][k] + f[k + 1][r] + s);
g[l][r] = max(g[l][r], g[l][k] + g[k + 1][r] + s);
}
}
int mn = INF, mx = -INF;
for (int l = 1; l <= n; l++) // 枚举起点,取所有长度为 n 的窗口
{
mn = min(mn, f[l][l + n - 1]);
mx = max(mx, g[l][l + n - 1]);
}
cout << mn << endl << mx << endl;
return 0;
}练习
小字说明:洛谷上与「加分二叉树」完全同型(中序固定 + 枚举根 + 乘法加分 + 前序回溯)的原生题目较少,P1040 本身即该型的代表与压卷题。上面两题都是更广义的「区间上枚举分界」区间 DP——一个把分割推到二维矩形、一个叠加「分成 k 段」的维度,用来巩固「枚举分割/根 + 按区间递推」的通用手感,而非同题换皮。

