0题目原文(本地存了一份)
原题在洛谷上(页头有链接)。别人的网站不归我们管,打不开、改版、题号调整都可能发生。 所以每个解析页都把题面转录一份存在本地,跟着仓库一起进版本库。
转录自洛谷 P2367,日期见页头。两边不一致时信原站。
题目背景:语文考试结束了,成绩还是一如既往地有问题。
题目描述
语文老师总是写错成绩,所以当她修改成绩的时候,总是累得不行。 她总是要一遍遍地给某些同学增加分数,又要注意最低分是多少。你能帮帮她吗?
输入格式
第一行有两个整数 n、p,代表学生数与增加分数的次数。
第二行有 n 个数,a₁ ~ aₙ,代表各个学生的初始成绩。
接下来 p 行,每行有三个数 x、y、z,代表给第 x 个到第 y 个学生每人增加 z 分。
输出格式:输出仅一行,代表更改分数后,全班的最低分。
数据范围
- 对于 40% 的数据,有
n ≤ 10³; - 对于 60% 的数据,有
n ≤ 10⁴; - 对于 80% 的数据,有
n ≤ 10⁵; - 对于 100% 的数据,有
n ≤ 5 × 10⁶,p ≤ n,学生初始成绩≤ 100,z ≤ 100。
输入输出样例
输入
3 2 1 1 1 1 2 1 2 3 1
输出
2
3 个学生初始都是 1 分;1 2 1 给第 12 个各加 1 分,3 个各加 1 分。
最后是 2 3 1 给第 22 3 2,最低分 2。上面那段输出是仓库里的 p2367.cpp 真跑出来的。
这道题考什么:差分。区间加、最后问最低分,只记变化量,最后还原一次。算法三行,真正决定分数的是读入。
最容易错的三处
- 读入是主项。满数据有两千万个整数,
cin默认要 3 秒多,超时三倍。关同步和scanf也不够,要手写快读。 d[y+1]要开到n+1。y可以等于n,这一行会写到数组外面。样例里y从没碰到n,所以对拍要专门造这一档。- 先读数据范围再动手。
n ≤ 5×10⁶同时决定了用差分、读入预算和数组大小。
能 AC 的:第 ③ 版(差分 + 手写快读)。它的读入约 0.36 秒,算法部分只有几分之一秒。
下面每一步里灰色的「深入」块是折叠的:那是这些结论怎么实测出来的,第一遍可以跳过。
1先盯住数据范围里那个数:n ≤ 5 × 10⁶
这道题的算法是第 6 章后半场的模板(差分,三行), 真正决定分数的是另一件事:
n ≤ 5 000 000,p ≤ n
⇒ 最坏要读 5×10⁶ + 3×5×10⁶ = 两千万个整数
⇒ 输入文件本身就有 100 MB 左右
而时限是 1 秒
★ 「读入」在这道题上不是常数,是主项。 第 ④ 步会把四种读法量给你看 ——
结论会有点吓人:连 scanf 都不够。
2第 ① 版:题目怎么说就怎么做(40 分)
// P2367 的第 ① 版:题目怎么说就怎么做 —— 每次操作,把 [x, y] 里每个人的成绩加上 z//// ★ 它在「对于 40% 的数据,n ≤ 10³」那一档上稳过(最多 10⁶ 次加法),// ⇒ 考场上的 40 分。⚠ 满数据 n = p = 5×10⁶、每次都覆盖全班 ⇒ 2.5×10¹³ 次加法。//// 慢在哪一眼可见:**同一个人被反复加了无数遍**,而最后只问一次最低分。// 第 6 章后半场的「关键的一步」就是从这句话里长出来的:**只记变化量**。
#include <bits/stdc++.h>using namespace std;
static int a[5000005];
int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n, p; cin >> n >> p; for (int i = 1; i <= n; i++) cin >> a[i]; for (int i = 0; i < p; i++) { int x, y, z; cin >> x >> y >> z; for (int j = x; j <= y; j++) a[j] += z; // ← 一个一个加 } int ans = INT_MAX; for (int i = 1; i <= n; i++) ans = min(ans, a[i]); cout << ans << '\n'; return 0;}点「运行 ▶」看结果
深入:一个一个加的秒表与外推(进阶)
本机实测(A 机 · WSL2 · Linux 6.18 / 12 线程 / 7 GB,2026-09-05 重测,独占, 各跑三次取中位数;最坏形状:每次操作都覆盖全班):
n = p |
① 一个一个加 | ② 差分 |
|---|---|---|
| 1 000(40% 那一档) | ★ 0.00 秒 | 0.00 秒 |
| 10 000 | 0.00 秒 | 0.00 秒 |
| 50 000 | 0.20 秒 | 0.00 秒 |
| 200 000 | 3.22 秒 | 0.00 秒 |
| 5 000 000(满数据) | 约 2000 秒(按 n² 外推) |
★ 0.10 秒 |
★ 那个外推值有两个互相独立的基准,而它们对得上:0.20 × 100² = 2000、3.22 × 25² = 2013。
⚠⚠ 这张表 2026-09-05 整个重测过(旧的一版是 0.02 / 0.44 / 7.75 / 约 4400,
2026-08-26 在 B 机上量的)——「秒数跨机器一律作废」是老规矩,
⚠ 而第 ⑤ 步下面那个对照把更要命的一层量了出来:旧数字是在有负载的时候量的。
★ 它在「40% 的数据 n ≤ 10³」那一档上稳过 —— 考场上是实打实的 40 分,
而写它只要五行。⚠ 但注意:这道题的部分分是按 n 分档的,
n ≤ 10⁵ 那一档(80%)它也过不去(10¹⁰ 次加法)。
3第 ② 版:差分 —— 只记变化量
d[x] += z;
d[y + 1] -= z; // ⚠ 数组要开到 n+1:y = n 时这一行会写 d[n+1]
最后对 d 求一遍前缀和,就还原出「第 i 个人一共加了多少」。
// P2367 语文成绩 —— 能 AC 的那一版:差分 + 快读//// 算法就是第 6 章后半场那三行:// 区间 [x, y] 每人加 z ⇒ d[x] += z; d[y+1] -= z;// 最后对 d 求一遍前缀和,就还原出每个人加了多少。// O(n + p) 代替 O(np)。//// ★★ 但这道题真正卡人的**不是算法,是读入**:n ≤ 5×10⁶、p ≤ n ⇒// 最坏要读 5×10⁶ + 3×5×10⁶ = **两千万个整数**,而时限只有 1 秒。// 本机实测(正文第 ④ 步):同一个差分算法,用默认的 cin 读要 3.5 秒,用快读 0.35 秒。// ⇒ 第 45 章那一节(四种读法差多少)在这道题上是**分数线**,不是趣味知识。//// 快读的原理:一次 fread 把一大块字节拿进来,自己拼数字,// 不经过任何格式解析(scanf 那套 `%d` 的解析器)。
#include <bits/stdc++.h>using namespace std;
static char ibuf[1 << 22];static size_t ipos = 0, ilen = 0;
static inline int gc() { if (ipos == ilen) { ilen = fread(ibuf, 1, sizeof(ibuf), stdin); ipos = 0; if (!ilen) return EOF; } return ibuf[ipos++];}static inline int readInt() { int c = gc(), s = 1, x = 0; while (c != EOF && (c < '0' || c > '9') && c != '-') c = gc(); if (c == '-') { s = -1; c = gc(); } for (; c >= '0' && c <= '9'; c = gc()) x = x * 10 + (c - '0'); return x * s;}
static int a[5000005];static int d[5000006]; // ⚠ 要开到 n+1:y = n 时会写 d[n+1]
int main() { int n = readInt(), p = readInt(); for (int i = 1; i <= n; i++) a[i] = readInt(); for (int i = 0; i < p; i++) { int x = readInt(), y = readInt(), z = readInt(); d[x] += z; d[y + 1] -= z; // ← 差分的全部内容就这两行 } int cur = 0, ans = INT_MAX; for (int i = 1; i <= n; i++) { cur += d[i]; // 前缀和还原「第 i 个人一共加了多少」 ans = min(ans, a[i] + cur); } printf("%d\n", ans); return 0;}点「运行 ▶」看结果
d[y+1]:y可以等于n,所以数组必须开到n + 1。 这是差分唯一能写错的地方,对拍档位 1 专门盯它。ans不用开long long:最低分最大是100 + 100 × p ≤ 100 + 5×10⁸, 离int的2.1×10⁹还差四倍。★ 这一笔仍然要算 —— 算完确认不用,和没算过是两回事。
4★★★ 这道题的第二关:四种读法,只有一种过得去
深入:四种读法的实测(进阶)
同一个差分算法,只把读入换掉:
// P2367 的第二关:同一个差分算法,四种读法差多少//// 用法:./p2367Read [n] [csv] 默认 n = 1000000(满数据是 5000000)// ★ 它自己造一份 P2367 形状的输入写进临时文件,再 freopen 回 stdin 读四遍 ——// 不需要喂输入。四遍的**答案必须一模一样**(csv 里的 same 就是钉这件事的)。//// 四种读法(第 45 章第 11 步解释过它们的区别,这里量的是**这道题上的**代价):// ① cin(默认,同步开着) ② cin + sync_with_stdio(false)// ③ scanf ④ 手写快读(fread 整块读进来自己拼数字)//// ⚠ 顺序不能换:关掉同步之后 cin 会自己预读一大块,之后再 freopen 换文件,// cin 缓冲里剩的就是上一份文件的残渣 ⇒ 「同步开着」那一趟必须排在最前面// (这条坑是第 45 章 read.cpp 里踩过的,照抄它的顺序)。
#include <bits/stdc++.h>#include <chrono>using namespace std;using namespace std::chrono;
static char path[64];static int a[5000005];static int d[5000006];
static void makeData(int n) { snprintf(path, sizeof(path), "/tmp/p2367-bench-%d.txt", (int)getpid()); FILE* f = fopen(path, "w"); if (!f) { fprintf(stderr, "写不出临时文件 %s\n", path); exit(1); } mt19937 rng(20260826u); fprintf(f, "%d %d\n", n, n); for (int i = 0; i < n; i++) fprintf(f, "%d%c", (int)(rng() % 101), i + 1 == n ? '\n' : ' '); for (int i = 0; i < n; i++) { int x = 1 + (int)(rng() % (unsigned)n); int y = x + (int)(rng() % (unsigned)(n - x + 1)); fprintf(f, "%d %d %d\n", x, y, (int)(rng() % 101)); } fclose(f);}static void reopen() { if (!freopen(path, "r", stdin)) { fprintf(stderr, "读不回临时文件\n"); exit(1); }}
/* 差分那三行 —— 四趟共用,所以四趟之间的差别只可能来自读入 */static int finish(int n) { int cur = 0, ans = INT_MAX; for (int i = 1; i <= n; i++) { cur += d[i]; ans = min(ans, a[i] + cur); } return ans;}static void reset(int n) { memset(d, 0, sizeof(int) * (size_t)(n + 2)); }
static char ibuf[1 << 22];static size_t ipos = 0, ilen = 0;static inline int gc() { if (ipos == ilen) { ilen = fread(ibuf, 1, sizeof(ibuf), stdin); ipos = 0; if (!ilen) return EOF; } return ibuf[ipos++];}static inline int readIntFast() { int c = gc(), x = 0; while (c != EOF && (c < '0' || c > '9')) c = gc(); for (; c >= '0' && c <= '9'; c = gc()) x = x * 10 + (c - '0'); return x;}
int main(int argc, char** argv) { int n = (argc > 1) ? atoi(argv[1]) : 1000000; bool csv = (argc > 2 && string(argv[2]) == "csv"); makeData(n); double ms[4]; int ans[4];
/* ① cin(默认,同步开着)—— 必须排第一趟 */ { reopen(); reset(n); auto t0 = steady_clock::now(); int m, p; cin >> m >> p; for (int i = 1; i <= m; i++) cin >> a[i]; for (int i = 0; i < p; i++) { int x, y, z; cin >> x >> y >> z; d[x] += z; d[y + 1] -= z; } ans[0] = finish(m); ms[0] = duration<double, milli>(steady_clock::now() - t0).count(); }
/* ② cin + sync_with_stdio(false) */ { reopen(); reset(n); auto t0 = steady_clock::now(); ios::sync_with_stdio(false); cin.tie(nullptr); int m, p; cin >> m >> p; for (int i = 1; i <= m; i++) cin >> a[i]; for (int i = 0; i < p; i++) { int x, y, z; cin >> x >> y >> z; d[x] += z; d[y + 1] -= z; } ans[1] = finish(m); ms[1] = duration<double, milli>(steady_clock::now() - t0).count(); }
/* ③ scanf */ { reopen(); reset(n); auto t0 = steady_clock::now(); int m, p; if (scanf("%d %d", &m, &p) != 2) { m = p = 0; } for (int i = 1; i <= m; i++) { if (scanf("%d", &a[i]) != 1) break; } for (int i = 0; i < p; i++) { int x, y, z; if (scanf("%d %d %d", &x, &y, &z) != 3) break; d[x] += z; d[y + 1] -= z; } ans[2] = finish(m); ms[2] = duration<double, milli>(steady_clock::now() - t0).count(); }
/* ④ 手写快读 */ { reopen(); reset(n); ipos = ilen = 0; auto t0 = steady_clock::now(); int m = readIntFast(), p = readIntFast(); for (int i = 1; i <= m; i++) a[i] = readIntFast(); for (int i = 0; i < p; i++) { int x = readIntFast(), y = readIntFast(), z = readIntFast(); d[x] += z; d[y + 1] -= z; } ans[3] = finish(m); ms[3] = duration<double, milli>(steady_clock::now() - t0).count(); }
remove(path); bool same = (ans[0] == ans[1] && ans[1] == ans[2] && ans[2] == ans[3]); if (csv) { const char* key[4] = { "cin", "nosync", "scanf", "fast" }; for (int k = 0; k < 4; k++) printf("%s,%.1f\n", key[k], ms[k]); printf("same,%d\nanswer,%d\n", same ? 1 : 0, ans[0]); return 0; } const char* name[4] = { "cin(默认,同步开着)", "cin + sync_with_stdio(false)", "scanf", "手写快读(fread)" }; /* ⚠ 中文是双宽的,%-30s 按字节数补空格会补歪 —— 照第 45 章 read.cpp 那套按显示宽度补 */ auto disp = [](const string& t) { int w = 0; for (unsigned char c : t) { if ((c & 0xC0) == 0x80) continue; w += (c < 0x80) ? 1 : 2; } return w; }; auto padR = [&](const string& t, int w) { return t + string(max(0, w - disp(t)), ' '); }; double best = *min_element(ms, ms + 4); printf("n = p = %d(和满数据比是 %.0f%%),四种读法跑同一个差分:\n\n", n, 100.0 * n / 5000000); for (int k = 0; k < 4; k++) printf(" %s %8.1f 毫秒 慢 %4.1f 倍\n", padR(name[k], 30).c_str(), ms[k], ms[k] / best); printf("\n四趟的答案%s(都是 %d)\n", same ? "完全一致" : "居然不一致!", ans[0]); return 0;}点「运行 ▶」看结果
本机实测(A 机 · WSL2 · 12 线程 · 2026-09-05 重测 · 独占 · 三次取中位数;
n = p = 5 × 10⁶ 满数据、随机区间。⚠ 这一栏量的是只读入,不含后面那三行差分):
| 读法 | 毫秒 | 比最快的慢 | 1 秒时限 |
|---|---|---|---|
cin(默认,同步开着) |
3 087.3 | 8.6 倍 | ✗ |
cin + sync_with_stdio(false) |
925.9 | 2.6 倍 | ⚠ 光读入就吃掉 93% 的时限 |
scanf |
1 064.4 | 3.0 倍 | ✗(⚠ 只超 6%) |
手写快读(fread) |
★ 358.7 | 1.0 倍 | ★ ✓ |
原来这四行写的是 8 139.7 / 2 676.6 / 3 029.1 / 682.3,旁边标着「独占」。 独占重测,四行一起快了 2.6 ~ 2.9 倍。⇒ 追下去只用了一个对照: 把 12 个核全占满再量一次 —— 快读那一行当场变成 690.7 毫秒, 而页面上那个旧值是 682.3,几乎一模一样。
⇒ ★★★ 所以那张表不是「机器变了」,是「量的时候机器不空」 ——
和第 51 章那一轮(「倍增查询 701.5 ms」独占重测是 366)是同一件事的第二次。
⚠ 而这一次的代价说得出具体数:四个判决里有两个的余量被吃掉了一个数量级 ——
scanf 从「超 3 倍」变成「只超 6%」,
关同步从「快了三倍还是超」变成「光读入就吃掉 93% 的时限」。
⇒ ★★ 而倍数这一列几乎没动(最慢比最快:独占 8.6 倍、满载 7.5 倍, 而绝对秒数差 2.6 倍)—— 这就是硬规矩第 5 条那句「优先写倍数,秒数只当量级」的全部理由: 倍数在空和不空两种情况下都活着,绝对秒数不是。
第 45 章第 11 步量过这四种读法的差别,那里的结论是
「cin 默认最慢、快读最快」。这道题把那张表变成了及格线:
- 大多数人的第一反应
cin(不关同步):3.1 秒,三倍超时。 - 加一句
ios::sync_with_stdio(false):0.93 秒 —— 快了 3.3 倍, 可光读入就吃掉 93% 的时限,后面那三行差分再一加就压在线上了。 - 换成
scanf:1.06 秒 —— ⚠ 只超 6%,可它还是超。 - 手写
fread快读:0.36 秒,过。
⇒ 判断要不要写快读,看的不是「题目难不难」,而是要读多少个数。
这道题两千万个 —— 而 scanf 一个数在这台机器上约 53 纳秒,光解析就 1.06 秒。
★ 经验值:读入量到 10⁶ 就该关同步,到 10⁷ 就该上快读。
5★ 对拍:600 轮,两档
// 数据生成器(P2367 对拍用):`./p2367Gen <seed> [level]`//// level 0(默认)随机 n <= 12、p <= 8,成绩 0..100,z 0..100,区间随机// level 1 专挑边界:x = 1、y = n、x = y,以及 z = 0//// ★ 为什么要有 level 1:差分唯一能写错的地方是那两个下标 ——// `d[y+1] -= z` 在 y = n 时会写到 d[n+1](数组必须开到 n+1,不然就是越界),// 而 `d[x] += z` 在 x = 1 时是数组的第一个位置。// 顺手随机的话,「y 正好等于 n」的概率是 1/n。
#include <bits/stdc++.h>using namespace std;
static mt19937 rng;static int ri(int lo, int hi) { return lo + (int)(rng() % (unsigned)(hi - lo + 1)); }
int main(int argc, char** argv) { unsigned seed = (argc > 1) ? (unsigned)atoi(argv[1]) : 1; int level = (argc > 2) ? atoi(argv[2]) : 0; rng.seed(seed); int n = ri(1, 12), p = ri(1, 8); printf("%d %d\n", n, p); for (int i = 1; i <= n; i++) printf("%d%c", ri(0, 100), i == n ? '\n' : ' '); for (int i = 0; i < p; i++) { int x, y; if (level == 1) { int kind = ri(0, 3); if (kind == 0) { x = 1; y = n; } // 整段 else if (kind == 1) { x = ri(1, n); y = n; } // 贴着右端 —— 会写 d[n+1] else if (kind == 2) { x = 1; y = ri(1, n); } // 贴着左端 else { x = y = ri(1, n); } // 只有一个人 } else { x = ri(1, n); y = ri(x, n); } printf("%d %d %d\n", x, y, level == 1 && ri(0, 3) == 0 ? 0 : ri(0, 100)); } return 0;}点「运行 ▶」看结果
check:viz 每档 300 轮,暴力当标准答案 —— 两档都是 300 / 300 逐字节相同。
★ 档位 1 盯的是 d[y+1]:y 正好等于 n 的时候,那一行会写到数组的最后一格之外。
顺手随机的话它的概率是 1/n,专门造就是每四次一次。
6三个版本并排
| 版本 | 算法 | 读入 | 满数据 | 分数 |
|---|---|---|---|---|
① p2367Brute |
一个一个加 O(np) |
cin 关同步 |
约 2000 秒 | ★ 40 分 |
② p2367Cin |
差分 O(n+p) |
cin 默认 |
3.13 秒 | ✗ 超时 |
③ p2367 |
差分 O(n+p) |
快读 | ★ 0.37 秒 | ★ 100 分 |
★ 中间那一行是这一页的全部意思:算法对了,仍然可能因为读入而挂掉。
⚠ 这张表的秒数是两份真程序端到端跑满数据(p2367Cin / p2367,输入从文件喂进去);
上一步那张表是 p2367Read 在一个进程里连跑四趟量的(每趟 freopen 重读同一份数据)。
★ 两组数几乎相等(3.13 ↔ 3.09、0.37 ↔ 0.36),而这件事本身有话说:
端到端减去只读入,剩下的就是「算法那一份」—— 这道题上它只有 0.01 ~ 0.04 秒。
⇒ 也就是说这一页从头到尾在量的那个东西,九成以上是读入
(第 34 章 P3366 那条「量什么都要先问一句:这个数里有没有别人的份」
在这儿是反过来用的 —— 减完之后发现别人的份就是全部)。
7★★ 第 38 章回头看:这道题该不该上树状数组?
深入:树状数组版的秒表与形状(进阶)
写第 38 章的题单时这道题又出现了一次 —— 那一章的角度是 「第 6 章用纯差分做过,而树状数组多出来的正是「可修改」三个字」。 这一页原来一个字都没提树状数组,所以补上这一节。 (★ 这是本书第三次「已经写过、但角度没覆盖」:前两次是 第 35 章的 P1776 和第 37 章的 P1177。)
把差分数组交给树状数组,这道题照样做得出来(和正解逐字节相同,两档 600 轮验过)。 可代价是白付的:
| 区间加 | 最后问最低分 | |
|---|---|---|
| ★ 差分 + 一次还原(第 ③ 版) | O(1) × p | O(n) 一遍扫完 |
| 树状数组 | O(log n) × p | ⚠ 每个位置都要问一次 O(n log n) |
⚠⚠ 可「它会不会 TLE」这句话,2026-09-05 重测之后要加一个主语:形状。
顶格 n = p = 5 × 10⁶ 端到端 |
★ 差分 | 树状数组 | 比 |
|---|---|---|---|
每次操作都覆盖 [1, n](生成器默认档,对暴力最坏的那一种) |
0.10 秒 | 0.23 秒 | 2.3 倍 ⇒ ★ 两版都过得去 |
| 随机区间(生成器档位 1) | 0.37 秒 | ⚠ 1.17 秒 | 3.2 倍 ⇒ ⚠ TLE |
本机实测(A 机 · WSL2 · Linux 6.18 / 12 线程 / 7 GB,2026-09-05 重测,独占;
/usr/bin/time 各跑三次取中位数;两版都用同一份快读,时限 1 秒)。
⚠ 旧的一版写的是「0.61 ~ 0.74 秒 vs 1.39 ~ 1.80 秒 ⇒ TLE」,没有点名形状
(而那两个数也是有负载时量的,见第 ⑤ 步那个对照)。
★★★ 而这两行的差别不是「做的活变多了」—— 换把尺子数一数,两档做的活几乎一样多
(p2367Shape.cpp,把读入那一份摘掉,只量树状数组自己那两层循环):
| update 摸的节点 | query 摸的节点 | update 碰过的 64 字节缓存行 | 这两层的耗时 | |
|---|---|---|---|---|
覆盖 [1, n] |
115 000 000 | 54 717 320 | ★ 21 | 195 ms |
| 随机区间 | 112 291 523(少 2.4%) | 54 717 320(★ 一个不差) | 625 001 | ⚠ 1606 ms |
⇒ 摸的节点数差 2.4%、query 那一层一个不差,秒表却差 8.2 倍 ——
差的全在「摸的地方散不散」上:
[1, n] 的区间加,永远从下标 1 往上爬 —— 1 → 2 → 4 → 8 → …,
五百万次操作全程只碰 21 个缓存行(1.3 KB,一直待在 L1 里);
换成随机端点就散在 625 001 行上(40 MB,比这台机器任何一级缓存都大),
差 29 762 倍。
★ 而差分自己也被同一件事推着走(0.10 → 0.37 秒,3.7 倍),主语一模一样。
⚠⚠ 顺带一条:这里的 8.2 倍比上面端到端那张表的 5.1 倍还大 —— 因为端到端里九成是读入,那一份两档都要付,把差别稀释掉了。 ⇒ 又是第 34 章 P3366 那条「量什么都要先问一句:这个数里有没有别人的份」, ★ 而这一次不减掉它是低估,不是高估。
⇒ ★★★ 于是「顶格 ≠ 最坏」在这一页多了一层: 同一份数据,对暴力是最坏的(每次加满 n 个),对树状数组反而是最舒服的。 ⇒ 「⇒ TLE」这种判决必须连形状一起说,不点名就是错的。 ⇒ 而这也是两把尺子打架的又一次 (那次是「次数一样、秒表差 9.5 倍」,这次是「次数差 2.4%、秒表差 8.2 倍」)。
★ 闸门里钉的是哪几个数:check:viz 钉的是摸的节点数(115 000 000 / 112 291 523、
query 两档都是 54 717 320)和碰过的缓存行(21 / 625 001)—— 这两样精确、跨机器不变;
秒表只钉一条很松的比值带(≥ 3 倍,而独占实测是 8.2 倍)。
⚠ 上面那些端到端的秒数只当量级看 —— 这一页刚刚吃过一次亏。
★★★ 而判据只有一句话,不用跑程序:
所有修改是不是都发生在所有询问之前? · 是 ⇒ 差分 + 最后统一还原一次就够,树状数组「能改」的本事一次都用不上(这道题); · 否(边改边问)⇒ 每问一次就得现算一遍前缀和
O(n)⇒ 非树状数组不可 (隔壁那道 P3368 就是它,两道题的区间加写法一模一样)。
⇒ ★ 这也是第 34 章 P1546、第 35 章 P1776 那句话的第三次: 「这个技巧在这道题上成立」和「这道题该用它」是两句话。
- ★ 先读数据范围再动手。
n ≤ 5×10⁶这个数同时决定了三件事: 要用差分、要开 2000 万个数的读入预算、以及数组开多大。 - ★★★ 「读入优化」不是玄学,是这道题的及格线。
只读入这一段:
cin3.09 秒 / 关同步 0.93 秒 /scanf1.06 秒 / 快读 0.36 秒,时限 1 秒。 ⇒ 读入量10⁶关同步,10⁷上快读。 ⚠ 而这四个数 2026-09-05 重测过 —— 旧的一版大了 2.6 倍, 因为它是在有负载的时候量的(第 ⑤ 步那个对照)。 - ⚠
d[y+1]要开到n+1。 差分唯一能写错的地方, 而它只在y = n时露头 —— 对拍要专门造这一档。