合并 / 删除类
2048·区间删除代价
本课摘要
合并 / 删除类课程回答“合并与删除过程怎样压缩成区间状态”。内容以2048·区间删除代价为主线,配合逐步推导、可编辑演示、例题与练习形成可复查的学习闭环。
- 判断合并 / 删除类的适用条件与状态边界
- 围绕“2048·区间删除代价”推导转移与计算顺序
- 用演示、复杂度分析和配套题目校验实现
本课目录 · 0 节
正在整理目录…
从两端取数:另一种拆区间的方式
石子合并里,我们靠枚举中间的分割点把区间拆成两半。但区间 DP 还有一类同样常见的场景:操作只发生在区间的两端——从两头拿、把两头删、比较两头。它们拆区间的方式不是「从中间断」,而是从两端收缩。
看一个具体博弈:桌上一排 4 个数 ,两名玩家轮流行动,每回合只能从最左或最右端拿走一个数,拿到的数计入自己得分。两人都想让自己得分尽量高。先手最多能领先对手多少分?
第一反应也许是贪心:每步拿两端里更大的那个。可这并不总对——此刻贪一个大的,可能把对手放进下一步更肥的位置。因为拿走一端后,剩下的又是一个连续区间,对手同样会最优应对,牵一发而动全身。这与石子合并的困境同源:局部最优不等于全局最优,得把「剩下那段对手能拿多少」也算进来。
关键观察:无论怎么拿,当前面对的永远是一段连续区间 ;一次行动只会把它变成去掉左端的 或去掉右端的 ——长度恰好少 1。区间结构再次浮现,这正是区间 DP 的入口,只不过转移从「枚举分割点」换成了「选哪一端」。
状态与转移:站在对手的肩膀上
定状态。设 表示:当轮到某位玩家、面对区间 时,他能取得的「自己所得 − 对手所得」的最大净胜差。用「净胜差」而非「绝对得分」,是这一类博弈 DP 的点睛之笔——它让双方都最优这件事变得可递推。
他有两种选择。若拿走左端 :这一分先进自己账户,随后对手面对子区间 ,对手在那段的最大净胜差正是 ——但那是站在对手视角的领先,换回我方视角要取负号。于是这一步我方净胜差 。拿右端同理。
边界:(只剩一个数,先手别无选择直接拿走,净胜差就是它)。答案: 即先手在整排上的最大净胜差;若还想还原先手实际得分,用总和 反推 。
同样地, 依赖的两个子区间 与 长度都比它短 1。所以递推仍不能按 或 顺序走,必须按区间长度由短到长——这是区间 DP 雷打不动的填表顺序。
本质
区间 DP 的两副面孔:石子合并从中间枚举分割点(一分为二,追加区间和),两端取数 / 删除类从两端收缩(每次砍掉一端,规模减一)。共同点是状态都是连续区间 、都按长度递推。博弈型再叠一层技巧:用「净胜差」定义状态,子问题的领先在换手时取负,一个 就把「双方都最优」编码进了转移。
跟着算一遍
用开头的例子(,下标 )走完整张三角表,重点盯住长度由短到长、以及「减去子区间」这一步:
看三角表一层一层长出来 · 枚举分界、两段合并
- 来源参与当前转移的依赖状态
- 当前正在计算或观察的状态
- 确定 / 最优已进入当前最优解或确定结果
- 已处理已经扫描、当前不再活跃的状态
- 非法越界、冲突或不可达状态
深化:相邻相等合并(248)与升维到二维
两端收缩是这一类的「入门形态」。把操作换成合并相邻元素并产生新值,就得到更有趣的一支——趣味十足的 248(脱胎自 2048):一排数字,相邻两个相等的可以并成一个「值 + 1」的数,不断合并,问最终能得到的最大数字。
它的状态回到枚举分割点,但含义变了:设 = 区间 若能反复合并缩成单个数字,则那个数字的值,否则记 (不可合成)。一段能缩成 ,当且仅当存在分割点 ,使左段 与右段 都能缩成同一个数 :
全盘答案是所有区间里最大的那个 ——注意不一定是整段 ,因为整排未必能缩成单值,但某个子段可以。这正是 248 计分「看棋盘上最大的数」的由来。下一节的演示会把这张「能否合成」的三角表画出来。
再往上一维。 一维的「合并连续区间」升到二维,就是棋盘分割(例题 P1436):把 棋盘沿横 / 竖线递归切成若干矩形。状态从一维的 膨胀成一个矩形的四个坐标 ,转移枚举「切在哪条横 / 竖线」——本质仍是枚举最后一次分割、把大区域拆成两块子区域。这条「一维合并 → 二维分割」的线,正好把区间 DP 平滑地接到 D 部分 · 网格 / 矩阵上的 DP。
看 248 怎样把相邻相等的合并起来
默认 :两个 并成 ,与原有的 里的一个凑成 再并成 ——全盘最大数字是 3(落在子区间 或 上,而整段 反而缩不成单值)。改改数值,观察哪些格能合成(非 0)、哪些卡住。
- 来源参与当前转移的依赖状态
- 当前正在计算或观察的状态
- 确定 / 最优已进入当前最优解或确定结果
- 已处理已经扫描、当前不再活跃的状态
- 非法越界、冲突或不可达状态
易错点:答案未必在右上角
两端取数型答案就在右上角 ;但 248 这类合成型,整段常常合成不了单值,右上角是 。务必在填表过程中用一个全局变量记下所有 的最大值,而不是直接输出 。这是初学者在 248 上最常见的翻车点。
例题
#include <iostream>
#include <algorithm>
using namespace std;
int n, a[300];
int dp[300][300]; // dp[l][r]:区间 [l,r] 能合成的单一数字(0 = 不可合成)
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
dp[i][i] = a[i]; // 单个数自成一块
}
int ans = 0;
for (int i = 1; i <= n; i++)
{
ans = max(ans, a[i]);
}
for (int len = 2; len <= n; len++) // ★外层枚举区间长度,由短到长
{
for (int l = 1; l + len - 1 <= n; l++)
{
int r = l + len - 1;
for (int k = l; k <= r - 1; k++) // 枚举分割点:两段要合成同一个数
{
if (dp[l][k] && dp[l][k] == dp[k + 1][r])
{
dp[l][r] = max(dp[l][r], dp[l][k] + 1);
}
}
ans = max(ans, dp[l][r]); // 答案是全盘所有区间里的最大数字
}
}
cout << ans << endl;
return 0;
}
// TAG: 区间DP 合并 248#include <iostream>
#include <cstring>
using namespace std;
const int INF = 0x3f3f3f3f;
int n;
int s[9][9]; // 二维前缀和:s[i][j] = 左上角 (1,1) 到 (i,j) 的总和
int f[16][9][9][9][9]; // f[k][x1][y1][x2][y2]:把该矩形切成 k 块的最小平方和
// 矩形 (x1,y1)-(x2,y2) 的总分
int sum(int x1, int y1, int x2, int y2)
{
return s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1];
}
int sq(int v)
{
return v * v;
}
// 记忆化:把矩形切成 k 块,返回最小的「各块得分平方和」
int dfs(int k, int x1, int y1, int x2, int y2)
{
int &cur = f[k][x1][y1][x2][y2];
if (cur != -1)
{
return cur;
}
if (k == 1) // 不再切,整块贡献一份平方
{
return cur = sq(sum(x1, y1, x2, y2));
}
cur = INF;
for (int x = x1; x <= x2 - 1; x++) // 横切:上 k1 块 + 下 k-k1 块
{
for (int k1 = 1; k1 <= k - 1; k1++)
{
cur = min(cur, dfs(k1, x1, y1, x, y2) + dfs(k - k1, x + 1, y1, x2, y2));
}
}
for (int y = y1; y <= y2 - 1; y++) // 竖切:左 k1 块 + 右 k-k1 块
{
for (int k1 = 1; k1 <= k - 1; k1++)
{
cur = min(cur, dfs(k1, x1, y1, x2, y) + dfs(k - k1, x1, y + 1, x2, y2));
}
}
return cur;
}
int main()
{
cin >> n;
for (int i = 1; i <= 8; i++)
{
for (int j = 1; j <= 8; j++)
{
cin >> s[i][j];
s[i][j] += s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1];
}
}
memset(f, -1, sizeof(f));
int tot = sum(1, 1, 8, 8);
// 最小方差 ⇔ 最小平方和:σ² = (Σxᵢ²)/n − x̄²,均值固定,只需最小化 Σxᵢ²
double variance = (double)dfs(n, 1, 1, 8, 8) / n - (double)tot / n * tot / n;
printf("%.3lf\n", variance);
return 0;
}
// TAG: 区间DP 二维 记忆化 棋盘分割
