题单 · 习题解析

洛谷 P5788 【模板】单调栈

★★★ 照题面**顶格随机**造一份数据,那个 O(n²) 暴力 **200 毫秒就跑完了**(严格递减上是 4.5 × 10¹² 次,差 15 万倍)⇒ 顺手随机会让你以为**不用学这一章**;★★★ 「弹栈少个等号」的触发条件是**三层**的 —— 「有重复元素」在顶格上满足 **4580** 次,而真正触发 **0** 次,★ 那个 0 **算得出来**(期望 n·lnR/R = 0.06),⚠ 值域压到 10⁶ 就有 54 个位置错;★★★ 一条草稿被打回:「严格递减是 while→if 的死穴」**错了** —— 那一档栈里永远只有一个元素,是**能证的 0**(另一个 0 是「一次都不弹」,两个 0 成因相反);⚠ 真正的关卡是 I/O —— 输入 28.2 MB **+ 输出 21.8 MB**,默认 cin 端到端 **735 ms / 时限 1 秒**;★★ 换成 std::stack 端到端只差 1.01 倍,**摘掉 I/O 才看得见那 1.19 倍**

原题:洛谷 P5788出自 第 35 章 单调栈与单调队列 的题单题面本地存档:2026-08-31
⚠ 先自己写一遍,再往下看

这一页不是标准答案,是一条阶梯:把大多数人真实会写出的第一版摆出来, 看它错在哪、慢在哪,再一步一步改。所以它对「已经自己动手撞过一次墙」的人最有用。
还没写过的话,先去写 —— 写出来的哪怕是错的,也比直接看这一页收获大。

0题目原文(本地存了一份)

为什么要在这儿抄一份题面

原题在洛谷上(页头有链接)。别人的网站不归我们管,打不开、改版、题号调整都可能发生。 所以每个解析页都把题面转录一份存在本地,跟着仓库一起进版本库。

转录自洛谷 P5788,日期见页头。两边不一致时信原站。

题目背景

模板题,无背景。

2019.12.12 更新数据,放宽时限,现在不再卡常了。

题目描述

给出项数为 n 的整数数列 a₁…ₙ

定义函数 f(i) 代表数列中第 i 个元素之后第一个大于 aᵢ 的元素的下标, 即 f(i) = min{ j | i < j ≤ n, a_j > aᵢ }。若不存在,则 f(i) = 0

试求出 f(1…n)

输入格式

第一行一个正整数 n

第二行 n 个正整数 a₁…ₙ

输出格式

一行 n 个整数表示 f(1), f(2), …, f(n) 的值。

数据规模与约定

对于 30% 的数据,n ≤ 100

对于 60% 的数据,n ≤ 5 × 10³

对于 100% 的数据,1 ≤ n ≤ 3 × 10⁶1 ≤ aᵢ ≤ 10⁹

时限 1 秒,内存 125 MB。

输入输出样例

输入

5
1 4 2 3 5

输出

2 5 4 5 0

1 后面第一个更大的是 4(下标 2);4 后面第一个更大的是 5(下标 5); 2 后面是 3(下标 4);3 后面是 5(下标 5);5 后面没有了 ⇒ 0。 ⚠ 这五个数互不相同 —— 记住这句话,第 ③ 步要用它。

1第一版:照定义两重循环

p5788Brute.cpp照定义写 —— 稳拿 60 分(n ≤ 5×10³ 那一档本机 4 毫秒)
// P5788 的参照物:照定义两重循环 —— O(n²)
//
// 题面给的 60% 那一档是 n ≤ 5×10³ ⇒ 这一版**稳拿 60 分**(2.5×10⁷ 次比较)。
// 而 100% 那档 n = 3×10⁶,它要 4.5×10¹² 次 —— 差六个数量级。
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
if (!(cin >> n)) return 0;
vector<int> a(n + 1);
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= n; i++) {
int ans = 0;
for (int j = i + 1; j <= n; j++)
if (a[j] > a[i]) { ans = j; break; }
cout << ans << (i == n ? '\n' : ' ');
}
return 0;
}
点一下即可编辑
输入(stdin)
输出
点「运行 ▶」看结果

题面把档次分得很清楚:60% 的数据 n ≤ 5 × 10³5000² / 2 = 1.25 × 10⁷ 次比较 ⇒ 这一版直接交上去就有 60 分,一分钟写完。

★★★ 但接下来这件事,是这一页最想让你记住的

顺手照题面顶格随机造一份数据(n = 3 × 10⁶aᵢ ≤ 10⁹),拿这个 O(n²) 暴力跑一遍:

200 毫秒。跑完了,而且答案全对。

时限是 1 秒。⇒ 照这个结果,你会得出「暴力就够了,单调栈是多余的」这个结论 —— 而它是错的。

★★★ 为什么随机数据骗得过它:换一把机器无关的尺子数一数

数「内层那个 for 一共比较了多少次」(p5788Count.cppbrute 那一行):

n 随机数据 严格递减 倍数
10 000 90 943 49 995 000 550
20 000 184 975 199 990 000 1081
40 000 382 972 799 980 000 2089

★ 随机那一列 n 翻倍、次数也只翻一倍(×2.03、×2.07)—— 它在随机数据上根本就是线性的, 每个位置平均只往右看 9.5 步就撞见更大的数了。 ★★ 而严格递减那一列每次 ×4.00,是干干净净的 O(n²)。 ⇒ 倍数在往上走(550 → 1081 → 2089),这就是 O(n²) 的签名P5019 那条)。

顶格 n = 3 × 10⁶ 上把这两条外推:随机约 2.9 × 10⁷ 次、严格递减 4.5 × 10¹² 次 ⇒ 差 十五万倍,后者本机要跑将近 40 分钟

⇒ ★★★ 这又是一次「顶格 ≠ 最坏」,但这一次的方向对人最危险: 前面几次是「顺手随机跑得动、真数据跑不动」,这一次是「顺手随机让你以为不用学这一章」。

2★ 关键一步:倒着扫,栈里存下标

p5788.cpp★ 这一版就能 AC(顶格 n = 3×10⁶,本机端到端 90 毫秒)
// P5788【模板】单调栈 —— ★ 这一版就能 AC
//
// f(i) = 第 i 个元素之后**第一个大于 a[i]** 的下标,不存在为 0。
//
// ★ 关键一步(本章第 5 步):倒着扫,栈里存**下标**,栈里的值自栈顶向下递增。
// 要给 i 找答案,就把栈顶那些「不比 a[i] 大」的全弹掉 —— 它们永远轮不到了:
// 任何比 i 更靠左的位置要往右看,都会先撞上 a[i]。
//
// ⚠ 这道题真正的关卡不是算法,是 I/O:n = 3×10⁶ ⇒ 输入约 32 MB、输出约 20 MB,
// 时限 1 秒。所以这一版读和写都是手写的(见本页第 ⑤ 步那张表)。
#include <bits/stdc++.h>
using namespace std;
static const int N = 3000005;
static int a[N], f[N], stk[N];
/* ⚠ 缓冲区是「读完就续」的窗口,不是「一口气全塞进来」——
第 12 章 P1010 那一跤:拿一个固定大小的大数组去装整份输入,
一旦估小了就**一声不吭地少读一截**,而所有版本一起错、对拍全绿。 */
static char ibuf[1 << 16];
static int ipos, ilen;
static inline char gc() {
if (ipos == ilen) { ilen = (int)fread(ibuf, 1, sizeof(ibuf), stdin); ipos = 0; if (ilen <= 0) return 0; }
return ibuf[ipos++];
}
static inline int rd() {
char c = gc();
while (c && (c < '0' || c > '9')) c = gc();
int x = 0;
while (c >= '0' && c <= '9') { x = x * 10 + (c - '0'); c = gc(); }
return x;
}
static char obuf[1 << 16];
static int opos;
static inline void flushOut() { fwrite(obuf, 1, opos, stdout); opos = 0; }
static inline void pc(char c) { if (opos == (int)sizeof(obuf)) flushOut(); obuf[opos++] = c; }
static inline void wr(int x) {
if (!x) { pc('0'); return; }
char t[12];
int c = 0;
while (x) { t[c++] = char('0' + x % 10); x /= 10; }
while (c) pc(t[--c]);
}
int main() {
int n = rd();
for (int i = 1; i <= n; i++) a[i] = rd();
int top = 0;
for (int i = n; i >= 1; i--) {
while (top && a[stk[top]] <= a[i]) top--; // ★ 相等也要弹:题面要的是「大于」
f[i] = top ? stk[top] : 0;
stk[++top] = i;
}
for (int i = 1; i <= n; i++) { wr(f[i]); pc(i == n ? '\n' : ' '); }
flushOut();
return 0;
}
点一下即可编辑
输入(stdin)
输出
点「运行 ▶」看结果
★ 一句话:把「已经没戏的」当场扔掉

倒着扫。栈里存下标,对应的值自栈顶向下递增

轮到 i 的时候,把栈顶那些「不比 aᵢ 大」的全弹掉 —— 它们这辈子都轮不到了: 任何比 i 更靠左的位置往右看,都会先撞上 aᵢ。 弹完之后栈顶要么空(f(i) = 0),要么就是第一个大于 aᵢ 的位置。

⚠ 那个 <= 里的等号是有讲究的:题面要的是「大于」,所以相等的也得弹掉。 第 ③ 步整整一步都在讲少了这个等号会怎样。

★★ 兑现本章第 6 步:「for 里套 while」为什么还是 O(n) —— 数一数就完了

本章第 6 步给的理由是均摊:每个下标一辈子只被压进去一次、也只能被弹出来一次。 这句话可以直接数(p5788Count.cppamort 那一行):

顶格 n = 3 × 10⁶
弹栈总次数 2 999 983
n 3 000 000

★ 少的那 17 次,就是跑完之后还留在栈里的那 17 个下标(它们一次都没被弹过)。 ⇒ 不是「大约 O(n)」,是严格小于 n

3★★★ 少一个等号:顺手数据抓不到,题面顶格也抓不到 —— 而两个 0 的原因不一样

p5788Ge.cpp✗ 弹栈条件少了等号 ⇒ 求的是「第一个大于等于」(官方样例照过)
// P5788 · 错法 ①:弹栈条件写成**严格小于** —— 相等的不弹
//
// while (top && a[stk[top]] < a[i]) top--; ← 错
// while (top && a[stk[top]] <= a[i]) top--; ← 对
//
// ⇒ 它求的是「第一个**大于等于** a[i] 的下标」,而题面要的是「第一个**大于**」。
// ⚠ 触发条件只有一句话:**存在 j > i,a[j] == a[i],而且 j 是第一个 ≥ a[i] 的位置**。
// ⇒ 序列里没有重复元素时,它和正解**逐字节相同**(本页第 ③ 步量了这条曲线)。
#include <bits/stdc++.h>
using namespace std;
static const int N = 3000005;
static int a[N], f[N], stk[N];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
if (!(cin >> n)) return 0;
for (int i = 1; i <= n; i++) cin >> a[i];
int top = 0;
for (int i = n; i >= 1; i--) {
while (top && a[stk[top]] < a[i]) top--; // ✗ 少了那个等号
f[i] = top ? stk[top] : 0;
stk[++top] = i;
}
for (int i = 1; i <= n; i++) cout << f[i] << (i == n ? '\n' : ' ');
return 0;
}
点一下即可编辑
输入(stdin)
输出
点「运行 ▶」看结果

它和正解只差一个字符:a[stk[top]] < a[i] 少了个 =。于是它求的是「第一个大于等于」。

★★★ 触发条件是三层的,而前两层之间差了七万倍

第一层最好写,也最没用:序列里得有重复元素。 可这一层在题面顶格上几乎必然成立 —— 生日悖论:3 × 10⁶ 个数扔进 10⁹ 的值域, 期望有 C(n,2)/R ≈ 4499 对重复。实测那份顶格数据里有 4580 对。

而两版在那份数据上逐字节相同。

真正的触发条件要多一句话:那两个相等的数之间,不能有更大的数 —— 只有这样,「第一个 ≥」才会落在那个相等的位置上。它的期望个数是一道能算的题:

位置 i 上的值是 v 时,右边第一个 ≥ v 的元素恰好等于 v 的概率 ≈ 1/(R − v + 1) ⇒ 期望位置数 ≈ n · H(R) / R ≈ n · ln R / R

顶格 n = 3 × 10⁶ 值域 10⁹(题面顶格) 值域 10⁶
重复对数(实测 / 期望) 4580 / 4499 4 497 615 / 4 499 998
真正触发的位置数(算出来的期望) 0(0.06) 54(43)
⇒ 两版逐字节比 相同 不同

⇒ ★★★ 第一层满足了 4580 次,而 bug 一次都没被触发。 这是本书那条曲线上最极端的一点 —— B3637 那次第一层 300 / 第二层 2(差 150 倍), 这一次第一层 4580 / 第二层 0。 ⇒ ★★ 第一层写得越「显然」,越要提防它一点区分度都没有; 而这一页还多给了一句:那个 0 是能算出来的(期望 0.06),所以它不是运气。

⚠ 但结论绝不是「这个等号无所谓」 —— 值域只要小一点,它就必错

aᵢ ≤ 10⁹上界,题目从没保证 aᵢ 撒得很开。 同样的 n,值域压到 10⁶完全在题面之内),顶格那份数据里就有 54 个位置两版不同。

小数据上更是一抓一个准:

300 轮 档 0 顺手(值域 10⁹ ★★ 档 1 值域 1~5 档 2 严格递增 档 3 严格递减
序列里有重复元素(第一层) 0 300 0 0
真正的触发条件(第二层) 0 296 0 0
⇒ 真被抓 0 296 0 0

★★ 第二层和抓获数一个不差(296 ≡ 296)—— 本书「触发条件 ≡ 抓获数」又中一次, ⚠ 而第一层差了 4 轮:「有重复元素」和「重复的那对之间没有更大的」不是一回事。 ★ 档 0 那个 0 是结构性的n ≤ 12 个数扔进 10⁹ 的值域,撞上的概率是百万分之几。 ⇒ 顺手写生成器时随手照抄题面的值域上限,正好把这个 bug 的藏身处让了出来。

4★★ while 降成 if:本章第 6 步那句话的反面,两个「精确的 0」原因还不一样

p5788Once.cpp✗ 一次只弹一个(官方样例打出 2 4 4 5 0,挡住了)
// P5788 · 错法 ②:把 while 改成 if —— 一次只弹一个
//
// ⚠ 这个错法的来处很具体:本章第 6 步那句「for 里套 while,为什么还是 O(n)」
// 正是给它准备的。看不懂均摊分析的人,很容易「为了保住 O(n)」把 while 降成 if。
//
// ⇒ 它一次只丢掉一个候选,于是栈里会留下比 a[i] 矮的元素,f[i] 指到它头上就错了。
// 触发条件:某一步**需要连着弹两个或更多**。
#include <bits/stdc++.h>
using namespace std;
static const int N = 3000005;
static int a[N], f[N], stk[N];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
if (!(cin >> n)) return 0;
for (int i = 1; i <= n; i++) cin >> a[i];
int top = 0;
for (int i = n; i >= 1; i--) {
if (top && a[stk[top]] <= a[i]) top--; // ✗ while 降成了 if
f[i] = top ? stk[top] : 0;
stk[++top] = i;
}
for (int i = 1; i <= n; i++) cout << f[i] << (i == n ? '\n' : ' ');
return 0;
}
点一下即可编辑
输入(stdin)
输出
点「运行 ▶」看结果
★ 这个错法的来处很具体

本章第 6 步专门解释过「for 里套 while 为什么还是 O(n)」。 没看懂那段均摊分析的人,很容易「为了保住 O(n)」主动把 while 降成 if ⇒ 它是看懂了复杂度、没看懂均摊的人才会写出来的错法 —— 正因如此才值得单列一步。

★★★ 一个被实测打回来的草稿:我以为严格递减是它的死穴,结果那一档是精确的 0

草稿里写着「严格递减的序列上每一步都要把整个栈弹空 ⇒ if 版必错 300 / 300」。 实测 0 / 300。

一行就能证明为什么:严格递减的序列倒着扫时,栈里永远只有一个元素 —— 每一步弹掉那唯一的一个、再压进自己。⇒ 弹一个就等于弹光,ifwhile 完全一样。

⇒ 于是这一页有两个能证的 0,而原因正好相反

300 轮 档 2 严格递增 档 3 严格递减
「需要连弹两个」的组数 0 0
为什么 栈里的值全都大于 aᵢ一次都不弹 栈里永远只有一个元素 ⇒ 弹一个就到底

★★ 两个 0 长得一模一样,成因一个是「从不弹」、一个是「弹一下就够」。 ⇒ 又一次「说清楚它算了什么,比说它错了有用得多」

★ 而这两个 0 的自检是白送的:另外两档一抓一个准
300 轮 档 0 顺手 档 1 值域 1~5 档 2 递增 档 3 递减
「某一步需要连弹 ≥ 2 个」 284 295 0 0
⇒ 真被抓 284 295 0 0

★★ 四个档全部一个不差(284 ≡ 284、295 ≡ 295、0 ≡ 0、0 ≡ 0)—— 对拍代码是活的,上面那两个 0 是真的。

5⚠ 这道题真正的关卡是 I/O —— 而这一页第一次让「输出」当主角

p5788Io.cppI/O 度量程序:自己造顶格数据,读四遍、写三遍
★★★ 先把这道题的账算清楚:输入 28.2 MB,输出 21.8 MB

n = 3 × 10⁶,每个 aᵢ 最多十位 ⇒ 输入 29 612 405 字节(28.2 MB); 输出是 3 × 10⁶ 个下标,最多七位 ⇒ 22 888 864 字节(21.8 MB)

⇒ ★★ 前面几章量过的都是读入那一侧(第 6 章 P2367第 12 章 P1226第 32 章 P3371)—— 这道题第一次让输出和输入同一个量级。

A 机 · WSL2 · 8 核 / 7 G · 2026-08-31 · 独占 · 顶格 n = 3×10⁶ · 3 次取最小
默认 cin / cout(同步开着) 624 ms
cin 关同步 94 ms 71 ms
scanf / fprintf 133 ms 101 ms
手写快读 / 快写 32 ms 26 ms

★ 那张表只量了 I/O 那一段。真正该看的是端到端(同一份单调栈,只换读写):

端到端 · 顶格 · 3 次取最小 时限 1 秒
手写快读 + 快写(p5788.cpp 90 ms
cin 关同步(p5788Sync.cpp 192 ms
scanf / printfp5788Scanf.cpp 262 ms
⚠ 默认 cin / coutp5788Cin.cpp 728 ms
(参照:第 ① 步那个 O(n²) 暴力,关同步的 cin 200 ms
p5788Cin.cpp⚠ 同一份算法,只换成默认 cin / cout —— 顶格 728 毫秒,余量只剩 1.37 倍
p5788Sync.cpp关同步:192 毫秒
p5788Scanf.cppscanf / printf:262 毫秒
⚠ 题面背景那两句话是情报,不是废话

「2019.12.12 更新数据,放宽时限,现在不再卡常了。」

⇒ 这句话的意思是:在那之前,默认 cin 是过不去的。 现在本机 728 ms / 时限 1000 ms —— 余量只有 1.37 倍,而评测机通常比本机慢。 ★ 而「关同步」这一行只要一句话就能拿到 3.8 倍:

ios::sync_with_stdio(false); cin.tie(nullptr);

⇒ 又一次「四种读法的倍数跨题几乎不变,变的是绝对时间,而分数线在绝对时间上」P1803 是 2 × 10⁶ 个数 / 3 秒 ⇒ 四种全够P2367 是 2 × 10⁷ 个数 / 1 秒 ⇒ 连 scanf 都不够; 这道题夹在中间 —— 关了同步就轻松,不关就在悬崖边上。 ★ 顺带印证了老结论:关同步的 cinscanf 还快(94 vs 133 毫秒)。

⚠⚠ 但这一条余量只有 1.39 倍(2026-09-01 独占重测:98 vs 136)—— 同一句话在第 37 章 P3378 那页的闸门里被 8 路并行晃翻过一次。 ⇒ 所以两处闸门现在都只钉「默认 cin 比另外三种都慢一倍以上、快读最快」这些 有 2 倍以上余量的部分;这两个相邻量的先后属于「独占实测」,不进闸门 (第 278 条秒表断言的主语是余量,不是量级)。

6★ 换一种存法值多少 —— 而这张表不量「只算法」那一列就会得出反的结论

p5788Stk.cpp★ 把手写 stk 数组换成 std::stack,I/O 完全相同
顶格 n = 3×10⁶ 手写 stk 数组 std::stack<int> 倍数
端到端 90 ms 90 ms 1.00
只跑单调栈那一层(摘掉 I/O) 21 ms 25 ms 1.19

★★ 端到端看着「换存法一分钱不值」,可那是因为 90 毫秒里有 69 毫秒是 I/O —— 真正在比的那一段被稀释成了 1%。 ⇒ 又一次「不量『只读入』那一列就会得出完全错误的结论」。 ★ 而两版的答案是逐字节相同的(同一棵搜索路径,f 数组逐个相等)—— 所以这张表里唯一的变量真的只有那个容器 (和 P1074 9.5 倍、P2925 1.13 倍、P2853 14 倍凑成一组: 次数和秒表量的从来不是同一件事)。

7★ 方向背成死的:模板在不同的书上朝向不同

p5788Fwd.cpp✗ 求成了「前面第一个更大的」(官方样例打出 0 0 2 2 0,当场挡住)
// P5788 · 错法 ③:方向背成了死的 —— 求的是「**前面**第一个更大的」
//
// 单调栈的模板在不同的书上朝向不同(有的从左往右、有的从右往左),
// 背了模板却没看题面那句「第 i 个元素**之后**」,就会写成这一版。
//
// ⚠ 它算的量是能说清楚的:g(i) = 第 i 个元素**之前**最近的、大于 a[i] 的下标。
// ⇒ 严格**递增**的序列上它恒输出 0,而正解恒输出 i+1(除了最后一个)。
#include <bits/stdc++.h>
using namespace std;
static const int N = 3000005;
static int a[N], f[N], stk[N];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
if (!(cin >> n)) return 0;
for (int i = 1; i <= n; i++) cin >> a[i];
int top = 0;
for (int i = 1; i <= n; i++) { // ✗ 扫描方向反了
while (top && a[stk[top]] <= a[i]) top--;
f[i] = top ? stk[top] : 0;
stk[++top] = i;
}
for (int i = 1; i <= n; i++) cout << f[i] << (i == n ? '\n' : ' ');
return 0;
}
点一下即可编辑
输入(stdin)
输出
点「运行 ▶」看结果

单调栈的模板有从左往右写的,也有从右往左写的。背了模板却没看题面那句 「第 i 个元素之后」,写出来的就是这一版。四个档全是 300 / 300,一测就死。

★ 它算的是什么,也能说清楚:g(i) = 第 i 个元素之前最近的、大于 aᵢ 的下标。 ⇒ 在严格递增的序列上它恒输出 0,而正解恒输出 i+1

8★ 对拍这一页

参照物就是第 ① 步那个 O(n²) 暴力 —— 题面 60% 那一档(n ≤ 5 × 10³)已经写明它跑得动。

300 轮(n 随机 8~12) 档 0 顺手 ★★ 档 1 值域 1~5 ★ 档 2 严格递增 ★ 档 3 严格递减
单调栈(正解) 0 0 0 0
std::stack 0 0 0 0
弹栈少个等号 0 296 0 0
while 降成 if 284 295 0 0
方向反了 300 300 300 300
★ 官方样例挡住了两个、放过了一个 —— 而放过的那个有结构上的原因
错法 抓获率(最狠的那一档) 官方样例挡住了吗
方向反了 300 / 300 挡住(打出 0 0 2 2 0
while 降成 if 295 / 300 挡住(打出 2 4 4 5 0
弹栈少个等号 296 / 300 放过 —— 样例那五个数互不相同

⇒ 又一次「它过了样例的第三种原因:这组样例在结构上问不出这个问题」。 ★ 而这一页把话说得更死:1 4 2 3 5 里没有重复元素 ⇒ 「第一个大于」和「第一个大于等于」在这组数据上是同一件事, 不是「碰巧对上了」,是恒等

9度量程序和生成器

p5788Count.cpp度量程序(本页所有数字都出自它)
p5788Gen.cpp(六个档位)数据生成器

10一页纸

★★ 关键的一步 倒着扫,栈里存下标;弹掉「不比 aᵢ 大」的 —— 它们永远轮不到了
★★ 均摊 顶格弹栈总次数 2 999 983 < n = 3 × 10⁶,不是「大约 O(n)」
★★★ 顶格 ≠ 最坏 暴力在顶格随机200 毫秒就跑完,在严格递减上 4.5 × 10¹² 次(差 15 万倍)
★★★ 触发条件三层 「有重复」顶格满足 4580 次,而真正触发 0 次 ——★ 那个 0 算得出来(期望 0.06)
★ 而它绝不是无所谓 值域压到 10⁶(仍在题面内)⇒ 顶格 54 个位置错、小数据 296 / 300
★★★ 草稿被打回 「严格递减是 if 版的死穴」错了 —— 那一档栈里永远只有一个元素,是能证的 0
★★ 触发条件 ≡ 抓获数 两个错法七个档一个不差(296 ≡ 296;284 / 295 / 0 / 0 全等)
⚠ 真正的关卡是 I/O 输入 28.2 MB + 输出 21.8 MB;默认 cin 端到端 728 ms / 时限 1 秒
★ 一句话拿 3.8 倍 ios::sync_with_stdio(false); cin.tie(nullptr); —— 而关同步的 cin 比 scanf 还快
★★ 换存法 端到端 1.00 倍被 I/O 稀释掉了),只算法那一层 1.19 倍