0题目原文(本地存了一份)
原题在洛谷上(页头有链接)。别人的网站不归我们管,打不开、改版、题号调整都可能发生。 所以每个解析页都把题面转录一份存在本地,跟着仓库一起进版本库。
转录自洛谷 P1063,日期见页头。两边不一致时信原站。
题目描述
在 Mars 星球上,每个 Mars 人都随身佩带着一串能量项链。在项链上有 N 颗能量珠。
能量珠是一颗有头标记与尾标记的珠子,这些标记对应着某个正整数。
并且,对于相邻的两颗珠子,前一颗珠子的尾标记一定等于后一颗珠子的头标记。
因为只有这样,通过吸盘(吸盘是 Mars 人吸收能量的一种器官)的作用,
这两颗珠子才能聚合成一颗珠子,同时释放出可以被吸盘吸收的能量。
如果前一颗能量珠的头标记为 m,尾标记为 r,后一颗能量珠的头标记为 r,尾标记为 n,
则聚合后释放的能量为 m × r × n(Mars 单位),新产生的珠子的头标记为 m,尾标记为 n。
需要时,Mars 人就用吸盘夹住相邻的两颗珠子,通过聚合得到能量,直到项链上只剩下一颗珠子为止。 显然,不同的聚合顺序得到的总能量是不同的,请你设计一个聚合顺序,使一串项链释放出的总能量最大。
例如:设 N = 4,4 颗珠子的头标记与尾标记依次为 (2,3) (3,5) (5,10) (10,2)。
我们用记号 ⊕ 表示两颗珠子的聚合操作,(j ⊕ k) 表示第 j, k 两颗珠子聚合后所释放的能量。
则第 4、1 两颗珠子聚合后释放的能量为:
(4 ⊕ 1) = 10 × 2 × 3 = 60。
这一串项链可以得到最优值的一个聚合顺序所释放的总能量为:
(((4 ⊕ 1) ⊕ 2) ⊕ 3) = 10 × 2 × 3 + 10 × 3 × 5 + 10 × 5 × 10 = 710。
输入格式
第一行是一个正整数 N(4 ≤ N ≤ 100),表示项链上珠子的个数。
第二行是 N 个用空格隔开的正整数,所有的数均不超过 1000。
第 i 个数为第 i 颗珠子的头标记(1 ≤ i ≤ N),
当 i < N 时,第 i 颗珠子的尾标记应该等于第 i+1 颗珠子的头标记。
第 N 颗珠子的尾标记应该等于第 1 颗珠子的头标记。
至于珠子的顺序,你可以这样确定:将项链放到桌面上,不要出现交叉, 随意指定第一颗珠子,然后按顺时针方向确定其他珠子的顺序。
输出格式
一个正整数 E(E ≤ 2.1 × 10⁹),为一个最优聚合顺序所释放的总能量。
说明/提示
NOIP 2006 提高组 第一题
输入输出样例
输入
4 2 3 5 10
输出
710
四颗珠子 (2,3) (3,5) (5,10) (10,2) 成环。题面里那个顺序释放
10×2×3 + 10×3×5 + 10×5×10 = 710。
⚠ 这一组样例挡住了「没破环」(250),
★ 而对本页那个最典型的坑 —— 断点写成 f[k+1][r] —— 完全无能为力(照样打 710)。
1★★ 关键的一步(一):区间的端点是「标记」,不是「珠子」
这道题看着像石子合并换了个代价公式,但它多了一层转译。
标记: a₁ a₂ a₃ a₄ (a₁)
珠子: ① ② ③ ④把相邻两颗珠子聚合,吃掉的正是它们中间那个标记:
(a_l, a_k) 和 (a_k, a_r) 合成 (a_l, a_r),释放 a_l × a_k × a_r。
⇒ 于是转移写成:
f[l][r] = max over k in (l, r) of ( f[l][k] + f[k][r] + a[l] × a[k] × a[r] )⚠⚠ 注意是 f[l][k] + f[k][r],不是 f[k+1][r]。
石子合并里写 k+1,是因为那里的区间装的是堆,一堆要么归左要么归右;
而这里 k 是一个标记,它同时是左半的右端和右半的左端。
// P1063 [NOIP 2006 提高组] 能量项链 —— ★ 这一版就能 AC。//// ★★ 关键的一步(一):**区间的端点是「标记」,不是「珠子」。**// 第 i 颗珠子是 (aᵢ, aᵢ₊₁) —— 它由**两个相邻的标记**夹出来。// 所以一段连续的珠子 = 一段连续的标记,而「合并」正好把中间那个标记吃掉://// f[l][r] = max over k in (l, r) of ( f[l][k] + f[k][r] + a[l] × a[k] × a[r] )//// ⚠ 注意断点那两项是 `f[l][k]` 和 `f[k][r]` —— **k 同时是左半的右端和右半的左端**,// 不是 `f[k+1][r]`。写成 `k+1` 就等于把「标记」当成「珠子」在切(见 p1063K1.cpp)。//// ★★ 关键的一步(二):**破环成链** —— 和 [P1880](/sol/p1880/) 一样,// 把标记复制一遍(2N 个),跑长度为 N 的区间,答案取每个起点的最大值。//// ⚠ 题面那句「`E ≤ 2.1 × 10⁹`」正好卡在 `int` 上限 2 147 483 647 的**里面一点点**// (余量只有 2.3%)—— 它是**出题人替你挡掉的**,不是能从数据范围推出来的// (推出来的上界是 `99 × 1000³ ≈ 9.9 × 10¹⁰`)。这里照旧用 `long long`,// 那样就不依赖那份信任。
#include <bits/stdc++.h>using namespace std;
int main() { int n; if (scanf("%d", &n) != 1 || n <= 0) return 0; int m = 2 * n; vector<long long> a(m + 2, 0); for (int i = 1; i <= n; i++) { if (scanf("%lld", &a[i]) != 1) return 0; a[i + n] = a[i]; // 破环成链 } a[m + 1] = a[1]; // 链上最后一个标记
// f[l][r]:把标记 l..r 之间那些珠子全合成一颗,最多能释放多少能量 vector<vector<long long>> f(m + 2, vector<long long>(m + 2, 0)); for (int len = 2; len <= n; len++) // len = 这一段有几颗珠子 for (int l = 1; l + len <= m + 1; l++) { int r = l + len; // 标记下标:l..r 共 len 颗珠子 long long best = 0; for (int k = l + 1; k < r; k++) // ★ k 是被吃掉的那个标记 best = max(best, f[l][k] + f[k][r] + a[l] * a[k] * a[r]); f[l][r] = best; }
long long ans = 0; for (int i = 1; i <= n; i++) ans = max(ans, f[i][i + n]); printf("%lld\n", ans); return 0;}点「运行 ▶」看结果
2★★ 关键的一步(二):破环成链(和上一页一模一样)
项链是个环 ⇒ P1880 那一套原样搬过来:标记复制一遍,
跑「恰好 N 颗珠子」的区间,答案取每个起点的最大值。
// ✗ P1063 的第一个坑:**没破环**(当成一条直的项链做)。//// 题面写得很清楚「项链」是一个环:第 N 颗珠子的尾标记等于第 1 颗的头标记。// 这一版只跑 f[1][n+1],等于规定「必须最后合并第 N 颗和第 1 颗之间那一处」。//// ★ 方向能先说死:它只是把可行方案砍掉了一部分 ⇒ **答案恒 ≤ 正解**。
#include <bits/stdc++.h>using namespace std;
int main() { int n; if (scanf("%d", &n) != 1 || n <= 0) return 0; vector<long long> a(n + 2, 0); for (int i = 1; i <= n; i++) if (scanf("%lld", &a[i]) != 1) return 0; a[n + 1] = a[1];
vector<vector<long long>> f(n + 2, vector<long long>(n + 2, 0)); for (int len = 2; len <= n; len++) for (int l = 1; l + len <= n + 1; l++) { int r = l + len; long long best = 0; for (int k = l + 1; k < r; k++) best = max(best, f[l][k] + f[k][r] + a[l] * a[k] * a[r]); f[l][r] = best; } printf("%lld\n", f[1][n + 1]); return 0;}点「运行 ▶」看结果
| 它 ≤ 正解 | ★ 300 / 300(可行方案被砍掉了一部分) |
| 300 轮被抓 | 251 |
| 官方样例 | ★ 挡住了(250 vs 710) |
3★★ 那个 `k+1`:几乎必被抓的 bug,官方样例偏偏放过了
// ✗ P1063 最典型的那个坑:断点写成了 `f[k+1][r]`。//// 石子合并里写的是 `f[l][k] + f[k+1][r]` —— 因为那里的区间装的是**堆**,// 一个堆要么属于左边要么属于右边,不能两边都算。//// ⚠ 而这道题的区间端点是**标记**:`k` 这个标记**同时是左半的右端和右半的左端**// (它正是那次合并被吃掉的那个标记)。所以必须写 `f[l][k] + f[k][r]`。// 照着上一题的手感写成 `k+1`,就等于把标记当成了珠子在切。//// ★ 页面上量过:它**恒 ≤ 正解**(300/300),而且 `n` 一大就几乎必被抓// (n ≤ 20 时 295/300,n ≤ 100 时 **300/300**)。// ⚠⚠ 可**官方样例偏偏放过了它**(照样打出 710)—— 因为样例的 `n = 4`// 正好是题面允许的最小值,切错一格几乎没差别。// ⇒ 又一次「样例挡不挡得住,只能一个一个试」。
#include <bits/stdc++.h>using namespace std;
int main() { int n; if (scanf("%d", &n) != 1 || n <= 0) return 0; int m = 2 * n; vector<long long> a(m + 2, 0); for (int i = 1; i <= n; i++) { if (scanf("%lld", &a[i]) != 1) return 0; a[i + n] = a[i]; } a[m + 1] = a[1];
vector<vector<long long>> f(m + 2, vector<long long>(m + 2, 0)); for (int len = 2; len <= n; len++) for (int l = 1; l + len <= m + 1; l++) { int r = l + len; long long best = 0; for (int k = l + 1; k < r; k++) best = max(best, f[l][k] + f[k + 1][r] + a[l] * a[k] * a[r]); // ✗ k+1 f[l][r] = best; } long long ans = 0; for (int i = 1; i <= n; i++) ans = max(ans, f[i][i + n]); printf("%lld\n", ans); return 0;}点「运行 ▶」看结果
n 的上限 |
5 | 8 | 20 | 100(题面顶格) |
|---|---|---|---|---|
| 它 ≤ 正解 | 300 / 300 | 300 / 300 | 300 / 300 | 300 / 300 |
| 被抓 | 261 | 286 | 295 | ★ 300 / 300 |
⇒ 这是个几乎必被抓的 bug ——⚠ 而官方样例放过了它。
原因很具体:样例的 n = 4,正好是题面允许的最小珠子数,切错一格几乎没有差别。
⇒ ★★ 本页因此给「样例是一测就死的过滤器」那条规律又添一个反例: 被样例放过的不一定是「偶尔才错」的 —— 也可能只是样例的规模太小。
4★★★ 题面那句「E ≤ 2.1 × 10⁹」——它不是推出来的,是出题人替你挡掉的
输出格式那一行写着:一个正整数 E(E ≤ 2.1 × 10⁹)。
而 int 的上限是 2 147 483 647 —— 2.1 × 10⁹ 正好在里面,余量只有 2.3%。
本书量过好几句「答案不超过 X」,它们都能从数据范围里乘出来: P1164 那句正好是 32 位够用的充分条件、 P1064 那句真实上界 160 000 且可达、 P1060 那句松了 666 倍。
这一句不行。 从数据范围能推出来的上界是:
一次聚合最多 1000 × 1000 × 1000 = 10⁹,一共聚合 N − 1 = 99 次
⇒ E ≤ 99 × 10⁹ = 9.9 × 10¹⁰ —— 比 int 大 46 倍⇒ 所以那句 E ≤ 2.1 × 10⁹ 是出题人对测试数据的额外承诺 ——
它挡掉的是一整类合法但答案巨大的输入。
★★★ 「保证」有两种:能从别处推出来的,和出题人替你挡掉的。前者可以信,后者要知道自己在信什么。
顶格 n = 100、标记全 1000 时的 E |
★ 99 000 000 000(正好撞上推出来的上界) |
顺手随机(n ≤ 8、标记 1~1000)300 轮里 E > 2.1 × 10⁹ |
60 轮(20%) |
| 其中 E > int 上限 | 54 轮 |
| 标记上限要压到多少才守得住 | ★ 300 |
⇒ ★★ 题面那句保证,把「数据范围顶格」那种数据整个排除掉了 ——
n = 100 且标记全 1000 是题目不会给的输入。
这是第 19 章 P1090 那条的又一次现场:
自己造顶格数据时,要连「输出侧的保证」一起满足,否则测的是一组题目不会给的输入。
| 生成器 | int 版和正解不一致 |
|---|---|
标记 ≤ 300(守得住 E ≤ 2.1 × 10⁹) |
★ 0 / 300 |
| 标记 ≤ 1000(顺手随机,守不住) | 54 / 300 |
★ 而那个 54 一个不差地等于「E 越过 int 上限」的 54 轮 ——
又一次「触发条件 ≡ 抓获数」。
⇒ 结论要说全:照题面,int 够;而写 long long 的好处是「不依赖那句承诺」
(和第 24 章 P5365 那条「截断让代码不依赖那段推理」是同一个道理)。
5★ 规模
链上 2N = 200 个标记,区间最长 N 颗珠子 ⇒ 转移 |
661 650 次 |
f 表 202 × 202 个 long long |
318 KB |
6度量程序、生成器和参照物
7一页纸
| ★★ 关键的一步(一) | 区间的端点是「标记」不是「珠子」 ⇒ 转移是 f[l][k] + f[k][r] + a[l]a[k]a[r] |
| ★★ 关键的一步(二) | 破环成链(和 P1880 一模一样) |
| 规模 | 转移 661 650 次,f 表 318 KB |
| 没破环 | 恒 ≤ 正解(300/300),被抓 251;★ 样例挡住 |
★★ 写成 f[k+1][r] |
n ≤ 100 时 300/300 被抓,⚠ 而官方样例放过它(样例 n = 4 太小) |
★★★ 「E ≤ 2.1 × 10⁹」 |
不是推出来的(推出来是 9.9 × 10¹⁰,大 46 倍),是出题人挡掉的 |
| ⇒ 生成器要守输出侧的保证 | 顺手随机 300 轮里 60 轮越过它、54 轮越过 int;标记压到 300 才守得住 |
用 int 行不行 |
守得住那句保证时 0 / 300;守不住时 54 / 300(≡ 越过 int 的 54 轮) |