CSP-S 2026 复赛突击教材

适用:2026 年 10 月 31 日(周六)14:30–18:30 · 4 题 · 每题 100 分 · 满分 400 · NOI Linux / C++

本教材由押题报告扩展而来,覆盖 8 个最可能考的专题。每个专题包含:考点精讲模板代码(可复制)推荐练习 + 详细解答。

使用方法:按「七日突击计划」推进;每道练习题先自己限时做(独立思考 ≥20 分钟),再点开折叠的详细解答对照。左侧进度条随你的打卡自动更新(保存在本机浏览器中)。
零基础? 先看 初学者预备知识(复杂度估算 / STL / 堆 / 取模 / freopen / 对拍),再按专题推进;每个知识点都有对应的详解页,正文中点击概念词即可跳转。临场查模板直接去 算法大全(可检索)。
押题为经验性推断,最终以 noi.cn 发布的《NOI 大纲(2025 年修订版)》与 CCF 官方通知为准。

一、考纲与考情速览

1.1 必须知道的官方信息

项目内容
时间2026-10-31(周六)14:30–18:30,共 4 小时
题量4 道编程题,每题 100 分(官方通知原文:"每次认证有四个题目")
环境NOI Linux,g++ 编译,文件 IO(freopen)
考纲《NOI 大纲(2025 年修订版)》,提高级知识点 ≈ 5–7 级
纠偏:网传"2026 年 CSP-S2 改为 3 题"与 CCF 官方通知矛盾,不要被带节奏,仍按 4 题、400 分备考。

1.2 大纲 2025 修订版对提高级的变化(押题核心依据)

变化知识点对你的意义
新增Manacher(NOI 级下放到 7 级)最明确的扩考信号 → 专题②
新增多维动态规划(6 级)2025 T4 已考三维 DP → 专题⑦
新增扫描线(7 级)矩形面积并 / 区间覆盖 → 专题⑧
新增bitset、离散化状压与值域技巧 → 专题④
删除次小生成树可战略放弃

二、七年命题规律(2019–2025,34 题)

题位难度走势高频考点近三年代表
T1黄 → 绿(门槛上升)贪心 / 模拟 / 枚举2024 决斗(双指针)· 2025 社团招新(反悔贪心)
T2绿 → 蓝DP / 状压 / 数据结构2025 道路修复(2k 枚举 + MST)
T3蓝 → 紫字符串 / 哈希 / 大模拟2023 结构体(大模拟)· 2025 谐音替换(AC 自动机)
T4紫 → 黑(黑题连续两年)图 / 树 / 高维计数2023 种树 · 2024 擂台游戏 · 2025 员工招聘(三维 DP)

三条周期性规律

  • 大模拟值得练习:2020、2023 年曾出现相关题型,样本不足以推断固定周期,也不能据此保证 2026 年出现(专题⑥)。
  • 计数 DP 压轴连续化:2024、2025 连续两年,官方简评点名表扬(专题⑦)。
  • 树上问题从不长期缺席:七年 5 年 T4 是树/图(专题⑤)。

三、七日突击计划(点击打卡)

进度会自动保存到本机。已完成请勾选,左侧进度条实时更新。

专题① 反悔贪心 押题 T1 · ★★★★★

对标真题:CSP-S 2025 社团招新(P14361)|推荐练习:P2949 Work Scheduling、P1484 种树

核心思想

贪心做错了怎么办?——允许"撤销"。先按普通贪心做,当出现约束不满足(或存在更优选择)时,把之前一个"性价比最低"的决定换掉。实现上通常是 堆 + 反悔标记,复杂度 O(n log n)。

三个经典变式(务必都见过)

  • 带截止时间的收益最大化(P2949):按时间排序,依次"占坑",坑满了就踢掉收益最小的。
  • 带配额上限的分配(2025 社团招新):先让每人去最优部门,超编的部门按"损失最小"把人转出去。
  • 不相邻选取(P1484 种树):选了位置 i 获益 a[i],同时禁止选 i−1、i+1;反悔技巧是把 i 换成 a[i−1]+a[i+1]−a[i] 的新节点放回堆中,实现"撤销 i 改选两个邻居"。

练习 P2949 Work Scheduling(详细解答)

题意:n 个工作(n ≤ 105),第 i 个截止时间为 di、报酬 pi。每个单位时间只能做一个工作,过期不能做,求最大报酬。

提示:先自己想 20 分钟 —— 排序 + 堆,考虑"反悔"发生在什么时候
把工作按截止时间 d 从小到大排序。维护一个小根堆存"已决定要做的工作的报酬"。逐个考虑工作 i:先把它加入方案(push,累加 ans)。若此时已选工作数 > di(即前 di 个时刻放不下),说明多选了一个,反悔:弹出堆中最小的报酬并从 ans 中减去——这个最小的要么是当前工作要么是之前某个,总之替换后方案仍合法且不劣。
详细解答(含正确性说明与代码)

为什么正确:按 d 排序后处理到 i 时,所有已选工作的截止时间都 ≤ di,因此"已选数量 ≤ di"是可行方案存在的充要条件(调度按截止时间从早到晚排即可)。堆中始终保持"当前 d 下的最优前 k 个收益",被弹出的恰是最该放弃的。复杂度 O(n log n)。

#include <bits/stdc++.h>
using namespace std;
int main() {
    int n; scanf("%d", &n);
    vector<pair<long long, long long>> a(n);  // (d, p)
    for (int i = 0; i < n; i++) scanf("%lld %lld", &a[i].first, &a[i].second);
    sort(a.begin(), a.end());                  // 按截止时间排序
    priority_queue<long long, vector<long long>, greater<long long>> pq;
    long long ans = 0;
    for (int i = 0; i < n; i++) {
        long long d = a[i].first, p = a[i].second;
        pq.push(p); ans += p;                  // 先贪心地"接下"
        if ((long long)pq.size() > d) {        // 超过 d 个时刻 → 必须反悔一个
            ans -= pq.top(); pq.pop();         // 踢掉收益最小的
        }
    }
    printf("%lld\n", ans);
    return 0;
}

走查一遍(4 个工作:(d,p) = (1,50)、(2,10)、(2,40)、(3,30)):

步骤当前工作操作堆(小根堆)ans
1d=1, p=50入堆,1 个 ≤ 1 ✓{50}50
2d=2, p=10入堆,2 个 ≤ 2 ✓{10, 50}60
3d=2, p=40入堆后 3 个 > 2 → 弹出最小的 10(反悔){40, 50}90
4d=3, p=30入堆,3 个 ≤ 3 ✓{30, 40, 50}120

第 3 步:先接下的工作②(报酬 10)在更优的工作③到来时被踢出——这就是"反悔"的具身化。

常见错误:① 忘开 long long(p ≤ 109,n 个加起来爆 int);② 误把条件写成 size() ≥ d——d 个时刻恰好能做 d 个工作。

练习 P1484 种树(进阶变式 · 解答要点)

解题要点(反悔贪心 + 双向链表)

题意:一排 n 个坑,收益 a[i],选中的坑不相邻,最多选 k 个,求最大收益。

关键反悔设计:每次取全局最大 a[i] 并选中;随后把 i 与左右邻居从候选中删除,并插入一个新位置,其权值为 a[i-1] + a[i+1] − a[i](可为负)。若之后选了这个新位置,等价于"撤销选 i,改选 i 的两个邻居"——恰好把不相邻约束修正回来。用双向链表维护左右邻居,大根堆 + 懒删除选最大。选 k 次取过程中最大累积和(收益为负时提前停止)。

复杂度:O(n log n)。对照:2025 社团招新的"按损失最小转出超编成员"本质就是这类反悔。

走查一遍(a = [3, 5, 2, 4],至多 k = 2 棵):

轮次堆顶(坑位:权值)动作累计收益
1②:5选②;①、③ 出局,插入"悔棋子"权值 3+2−5 = 05
2④:4选④(与②不相邻,合法)9
停止悔棋子:0堆顶 ≤ 0,再选不赚,停止("至多 k 棵"的体现)9

验证:不相邻选法中 {②,④} = 5+4 = 9 确为最大收益。

专题② Manacher 与回文 押题 T2/T3 · ★★★★★

大纲 2025 修订版将 Manacher 从 NOI 级下放到提高级 7 级——这是最明确的扩考信号

核心知识

  • Manacher:O(n) 求出以每个位置为中心的最长回文半径。技巧:插入 # 统一奇偶长度;利用已知回文的对称性 p[i] = min(R−i, p[2C−i]) 跳过重复比较。复杂度 O(n)。
  • 回文相关 DP 常客:最少切割次数(区间 DP 或以 i 结尾转移)、回文子序列个数、回文划分计数。
  • 考点组合趋势:Manacher/哈希 求出所有回文信息后,再套一层计数或贪心,这是"模板 + 思维"的典型命题方式。

练习 P3805【模板】Manacher(详细解答)

题意:给定字符串(|s| ≤ 1.1×107),求最长回文子串长度。

提示:如何用 # 统一奇偶?为什么 p[i] 可以从对称位置继承?
构造 t = "^#a#b#...#$"(首尾哨兵防止越界)。所有回文中心在 t 中的半径 p[i] 恰等于原串中回文的长度。维护当前右边界最远的回文 [C−p[C], C+p[C]](右端 R)。若 i 在 R 内,则 i 关于 C 的镜像 2C−i 的答案可继承,取 min(R−i, p[镜像]) 作为起点,再向两侧暴力扩展。总扩展次数均摊 O(n)。
详细解答(含代码与易错点)
#include <bits/stdc++.h>
using namespace std;
int main() {
    ios::sync_with_stdio(false); cin.tie(nullptr);
    string s; cin >> s;
    string t = "^#";
    for (char c : s) { t += c; t += '#'; }
    t += '$';                              // 哨兵保证 while 不越界
    int n = t.size(), C = 0, R = 0, ans = 0;
    vector<int> p(n, 0);
    for (int i = 1; i < n - 1; i++) {
        p[i] = (i < R) ? min(R - i, p[2 * C - i]) : 0;
        while (t[i + p[i] + 1] == t[i - p[i] - 1]) p[i]++;   // 暴力扩展
        if (i + p[i] > R) { C = i; R = i + p[i]; }           // 更新最右回文
        ans = max(ans, p[i]);
    }
    cout << ans << "\n";
    return 0;
}

走查一遍(s = "abba",t = "^#a#b#b#a#$",p[i] 为臂长,原串回文长度 = p[i]):

it[i]p[i]来源对应原串回文
2a1扩展"a"
4b1扩展"b"
5#(正中)4扩展"abba"(最长 ✓)
6b1镜像照抄(i′=4)"b"
7#0镜像照抄(i′=3)无
8a1镜像照抄(i′=2)"a"

i = 5 处理完后 R 推到 9,此后 i = 6~9 全部从镜像位置直接抄答案、零扩展——这就是 O(n) 的来源。

易错点:① 哨兵字符必须与 # 及所有字母不同(^ 和 $);② 千万别写 cin 不关同步(1e7 数据会 TLE);③ 若题目问"原串中的回文位置",中心 i 在原串的坐标是 (i−2)/2,长度即 p[i]。

组合训练:会模板后做"回文划分计数"——f[i] = Σ f[j](若 s[j..i] 是回文),用 Manacher/哈希 O(n²) 或 Eertree O(n) 实现,是 2026 最可能的出题方式。

专题③ AC 自动机与多模式匹配 押题 T3 · ★★★★★

对标真题:CSP-S 2025 谐音替换(P14363,用 ACAM+fail 树满分)|推荐练习:P5357、P3796

核心知识

  • AC 自动机 = Trie + KMP 的 fail 指针:fail[u] 指向 u 的最长真后缀对应的 Trie 节点。BFS 建 fail 时顺便做"路径压缩"(ch[u][c] 直接指向转移目标),文本串匹配无需跳 fail 链。
  • 统计出现次数:文本串每个位置在自动机上走一步并 cnt[cur]++;随后按 BFS 序逆序做 cnt[fail[v]] += cnt[v](把 fail 树上子树和求出来),每个模式串末节点的 cnt 即出现次数——这正是"fail 树"思想的入门。
  • 2025 真题把匹配对象抽象成"A{B D{C"式的变量拼接串,考的是"把题面建模成模式串"的能力,而非裸模板。

练习 P5357【模板】AC 自动机(二次加强版)详细解答

题意:n 个模式串(总长 ≤ 2×105)、一篇文本(≤ 2×106),输出每个模式串在文本中的出现次数(可重叠)。注意:不能暴力跳 fail(会被 aaaa... 卡成 O(n²)),必须用 fail 树统计。

提示:为什么暴力跳 fail 会 TLE?cnt 怎么从子节点汇到 fail 父节点?
文本每走一步可能沿 fail 链跳 O(长度) 次,a…a 型数据下总跳数达 O(文本×模式长)。正确做法:只给当前节点打 +1 标记,最后"一个节点的出现次数 = 它的 cnt 加上所有 fail 指向它的节点的 cnt 之和"——按 BFS 序从深到浅累加即可(fail[v] 一定比 v 浅,在 BFS 序中排在 v 之前,因此逆 BFS 序累加时父节点一定在子节点之后被更新,是安全的)。
详细解答(含完整代码)
#include <bits/stdc++.h>
using namespace std;
const int N = 200005;                       // 模式串总长 + 1
int ch[N][26], fail[N], idx = 0;            // Trie(已做转移压缩)
int pos[N]; long long cnt[N]; int q[N];
int insert_(const char *s, int id) {
    int u = 0;
    for (; *s; s++) {
        int v = *s - 'a';
        if (!ch[u][v]) ch[u][v] = ++idx;
        u = ch[u][v];
    }
    return pos[id] = u;
}
void build() {                              // BFS 建 fail + 转移压缩
    int h = 0, t = 0;
    for (int c = 0; c < 26; c++) if (ch[0][c]) q[t++] = ch[0][c];
    while (h < t) {
        int u = q[h++];
        for (int c = 0; c < 26; c++) {
            int v = ch[u][c];
            if (v) { fail[v] = ch[fail[u]][c]; q[t++] = v; }
            else    ch[u][c]  = ch[fail[u]][c];
        }
    }
}
int main() {
    int n; scanf("%d", &n);
    static char buf[200006];                     // 单个模式串长度 ≤ 2e5
    for (int i = 0; i < n; i++) { scanf("%s", buf); insert_(buf, i); }
    build();
    static char text[2000006]; scanf("%s", text);
    int u = 0;
    for (char *p = text; *p; p++) { u = ch[u][*p - 'a']; cnt[u]++; }   // 只打标记
    // q[0..idx-1] 为 BFS 序(共 idx 个非根节点),倒序累加 = fail 树自底向上求子树和
    for (int i = idx - 1; i >= 0; i--) cnt[fail[q[i]]] += cnt[q[i]];
    for (int i = 0; i < n; i++) printf("%lld\n", cnt[pos[i]]);
    return 0;
}

走查一遍(模式串 {he, she},文本 "ushers"):

阶段动作结果
扫描 uroot 无此边,停在 root—
扫描 s、h、e沿 Trie 走到 she★cnt[she] = 1(只打标记,不跳 fail)
扫描 r、s沿压缩转移继续走无新标记
逆 BFS 序累加cnt[she] 上缴给 fail 父节点cnt[he] += 1 → he 出现 1 次 ✓("she" 里藏着 "he")

期望输出:he → 1,she → 1。若逐次跳 fail 链统计,a…a 型数据会被卡成 O(n²)——子树和的意义就在于此。

复杂度:O(Σ|模式| × 26 + |文本|)。易错点:① 累加必须沿 BFS 序逆序(fail[u] 的 BFS 序小于 u);② 二次加强卡常,读入用 scanf/快读;③ cnt 开 long long(大文本同一位置可被统计多次)。

进阶对照(P3796 最出现次数的模式串):同样的 cnt 统计,最后扫一遍 end 节点取 max,注意"重复答案按输入序输出"。套路完全一致。

专题④ 状压 + 图连通 押题 T2 · ★★★★

对标真题:CSP-S 2025 道路修复(P14362)、CSP-S 2022 假期计划(P8817)|推荐练习:P1171、bitset 练手

核心思想

  • 识别信号:题面出现"某类关键对象个数 k ≤ 15~20",其余规模很大 → 对关键对象做 2^k 状压枚举,其余部分用多项式算法(MST / 最短路 / 贪心)。
  • 2025 真题结构:k ≤ 10 个乡镇,每个乡镇有开通费 + 到各城市的连边费;2^k 枚举开通集合 → 并入新图跑 MST。优化点:基础图的 MST 树边只有 n−1 条,先把边排序到循环外,循环内只处理与所选乡镇相关的边,复杂度 O(2^k·(n+k)·k·α)。
  • bitset(大纲新增):把 0/1 集合压成机器字做与/或/移位,典型用法:可达性传递 reach[v] |= reach[u]、0/1 背包按位优化、bitset 求二维偏序。

练习 P1171 售货员的难题(TSP,详细解答)

题意:n ≤ 20 个城市,完全图距离矩阵 w,从城市 1 出发经过每个城市恰好一次回到 1,求最短回路。

提示:状态怎么设计?为什么是 O(2^n·n²) 而不是 O(n!)?
dp[S][i] = 已访问集合为 S、当前停在城市 i 的最短路径长(起点 0 已含在 S 中)。转移:枚举下一个城市 j∉S,dp[S|1<<j][j] = min(dp[S][i] + w[i][j])。状态数 2^n×n,每个转移 O(n),最终 O(2^n·n²) ≈ 20²×2^20 ≈ 4×10^8,可过。
详细解答(含代码)
#include <bits/stdc++.h>
using namespace std;
int n, w[20][20], dp[1 << 20][20];
int main() {
    scanf("%d", &n);
    for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) scanf("%d", &w[i][j]);
    memset(dp, 0x3f, sizeof dp);
    dp[1][0] = 0;                                   // 只访问过起点
    for (int S = 1; S < (1 << n); S++)
        for (int i = 0; i < n; i++) {
            if (!(S >> i & 1) || dp[S][i] == 0x3f3f3f3f) continue;
            for (int j = 0; j < n; j++)
                if (!(S >> j & 1))
                    dp[S | 1 << j][j] = min(dp[S | 1 << j][j], dp[S][i] + w[i][j]);
        }
    int ans = INT_MAX;
    for (int i = 0; i < n; i++) ans = min(ans, dp[(1 << n) - 1][i] + w[i][0]);
    printf("%d\n", ans);
    return 0;
}

走查一遍(n = 3,距离矩阵 w:0↔1 为 1,1↔2 为 3,0↔2 为 2):

状态 S停在 if[S][i]来源
{0}00起点
{0,1}110→1
{0,2}220→2
{0,1,2}21+3 = 40→1→2
{0,1,2}12+3 = 50→2→1
答案(加回起点)min(4 + w[2][0], 5 + w[1][0]) = min(4+2, 5+1) = 6

两条回路 0→1→2→0 与 0→2→1→0 长度相同,都是 6。注意最后一步"+ w[i][0] 回起点"不能漏。

易错点:① 枚举 S 时内层直接跳过不合法状态(上面写法已保证);② 内存 2^20×20×4B ≈ 84MB,若 MLE 用滚动数组或把 dp 压成 dp[S] 为 vector;③ 实际考点常变形为"2^k 枚举 + MST"(道路修复型),TSP 是打基础。

bitset 练手(P2709 小 B 的询问 · 要点):莫队 + 桶统计,把"某颜色出现次数是否为奇数/平方贡献"用 bitset 维护,体会"位运算压集合"的写法即可,不必追求满分。

专题⑤ 树形 / 换根 DP 押题 T3/T4 常客 · ★★★★

对标真题:CSP-S 2023 种树(P9755)、2022 数据传输(P8820)|推荐练习:P1395 会议、P3047

核心知识

  • 换根 DP(re-rooting)三板斧:① 第一次 DFS 自底向上算"以 1 为根"的答案;② 第二次 DFS 自顶向下把根从父亲"搬"到儿子:ans[child] = ans[parent] + (n − sz[child]) − sz[child](距离和型);③ 注意取最小/最小时输出最小编号等细节。
  • 树上常见模型:树上调度(二分天数 + 最晚期限贪心,2023 种树)、树上路径 k≤3 矩阵 DP(2022 数据传输)、子树内统计 + 全局数据结构。

练习 P1395 会议(详细解答)

题意:n 个村庄、n−1 条长度为 1 的路构成树。选一个村庄开会,使其他村庄到它的距离总和最小。输出(编号最小的)最优村庄编号与距离和。

提示:以 1 为根算出每个点的子树大小与距离和后,根从 u 移到儿子 v,距离和怎么变?
设 f[u] 为以 u 为开会村庄时的距离总和。把根从 u 移到相邻的 v:v 子树内的 (sz[v] 个) 村庄每个近 1,其余 (n − sz[v]) 个村庄每个远 1。所以 f[v] = f[u] + (n − sz[v]) − sz[v]。先 DFS1 求 sz 与 f[1],再 DFS2(或 BFS)沿树递推所有 f,取最小、同值取最小编号。
详细解答(含代码)
#include <bits/stdc++.h>
using namespace std;
const int N = 100005;
vector<int> g[N];
long long sz[N], f[N];
int n;
void dfs1(int u, int fa, long long d) {          // 算子树大小与 f[1]
    sz[u] = 1; f[1] += d;
    for (int v : g[u]) if (v != fa) { dfs1(v, u, d + 1); sz[u] += sz[v]; }
}
void dfs2(int u, int fa) {                       // 换根递推
    for (int v : g[u]) if (v != fa) {
        f[v] = f[u] + (long long)(n - sz[v]) - sz[v];
        dfs2(v, u);
    }
}
int main() {
    scanf("%d", &n);
    for (int i = 1, x, y; i < n; i++) { scanf("%d%d", &x, &y); g[x].push_back(y); g[y].push_back(x); }
    dfs1(1, 0, 0); dfs2(1, 0);
    long long best = LLONG_MAX; int id = 0;
    for (int i = 1; i <= n; i++)
        if (f[i] < best) { best = f[i]; id = i; }    // 顺序遍历 → 天然取最小编号
    printf("%d %lld\n", id, best);
    return 0;
}

走查一遍(链 1—2—3,n = 3):第一遍 dfs 得 sz = [3,2,1](1 号起)、f[1] = 深度和 0+1+2 = 3;第二遍换根:f[2] = f[1] + (3−2) − 2 = 2,f[3] = f[2] + (3−1) − 1 = 3。最小距离和 2 在村庄 2 ✓(中间点开会更公平,符合直觉)。

易错点:① 链形树下距离和最大约 n²/2 ≈ 1.3×109(n ≤ 5×104),已逼近 int 上限,稳妥起见用 long long;② 递归深度——若 n 达 106 级别需改成 BFS/迭代栈(考场上 NOI Linux 栈与内存限制相同,一般安全,但要有意识);③ "同值取最小编号"这类输出细节是真实考点。

进阶对照(P3047 Nearby Cows · 要点):求每个点周围 k 步内的权和——维护"向上 i 层祖先的子树和"的前缀套 DP(f[u][j] 覆盖子树内、g[u][j] 覆盖子树外),体会"子树内 + 子树外"的拆分。

专题⑥ 大模拟 押题 T3 · ★★★(周期到期)

对标真题:CSP-S 2020 儒略日(P7075)、2023 结构体(P9754)|推荐练习:P1563 玩具谜题、P1514

核心方法论(大模拟拼的不是算法,是工程)

  1. 读题三遍,先抄规则清单:把题面所有规则逐条编号抄在草稿纸上,漏一条=全盘皆输。
  2. 状态设计成 struct:位置、方向、计数等打包,转移写成独立函数,主循环只做调度。
  3. 方向/循环偏移用同余:环形结构 (pos + step % n + n) % n;P1563 的坑:玩具朝向 face(0 朝内/1 朝外)× 指令 dir(0 左/1 右)——face == dir 时下标减小,否则增大(画图验证!这是本题唯一难点)。
  4. 对拍:写一个暴力 O(n²) 版本 + 随机数据生成器,本地对拍 10 分钟胜过盯着代码找 1 小时。

练习 P1563 玩具谜题(详细解答)

题意:n 个玩具围成圈,每个有朝向(0 朝内圈、1 朝外圈)和职业名;m 条指令(0 左数 s 个、1 右数 s 个),从第 0 个玩具出发,求最终玩具。

详细解答(含方向推导与代码)

方向推导(务必自己画圈验证):朝内的玩具,"左边"是逆时针方向(下标减小);朝外的玩具,"左边"反而是顺时针(下标增大)。归纳:face 与 dir 相同 → 下标减 s;不同 → 下标加 s。

走查一遍(n = 4,face = [0,1,0,1],从 0 号出发,指令:(0 左, s=1)、(1 右, s=2)):

指令当前 pos(face)dir 与 face移动计算新 pos
0 左数 10(朝内 0)相同 → 减(0 − 1 + 4) % 43
1 右数 23(朝外 1)相同 → 减(3 − 2 + 4) % 41

最终停在 1 号玩具。减法分支后的 + n 再 % n 是防负数的关键。

#include <bits/stdc++.h>
using namespace std;
int main() {
    int n, m; scanf("%d%d", &n, &m);
    vector<int> face(n); vector<string> name(n);
    for (int i = 0; i < n; i++) {
        char s[12]; scanf("%d %s", &face[i], s); name[i] = s;
    }
    int pos = 0;
    while (m--) {
        int dir; long long s; scanf("%d%lld", &dir, &s);
        if (dir == face[pos]) pos = ((pos - s % n) % n + n) % n;   // 同向:逆着编号走
        else                  pos = (pos + s) % n;
    }
    printf("%s\n", name[pos].c_str());
    return 0;
}

易错点:① s 可达 109,先模 n 再运算,且减法后要 +n 防负;② "同向相减"记错方向就全错——考场上用样例验证(样例一定能检验方向规则);③ 面向 2026:真正的考场大模拟(结构体/儒略日级别)会把本题材放大 10 倍,务必练"规则清单 + 分函数 + 对拍"三件套。

进阶对照(P1514 引水入城 · 要点):第一问 BFS 可达性;第二问把"每个第一行点能覆盖的最后一段区间"求出后做区间覆盖贪心——与 2024 超速检测的区间选点同族,模拟+贪心复合是流行出题法。

专题⑦ 多维计数 DP 与容斥 押题 T4 · ★★★★★

大纲新增"多维动态规划"|对标真题:CSP-S 2025 员工招聘(P14364)|推荐练习:P1450、P7914

核心知识

  • 状态设计心法:把影响后继决策的最小信息塞进状态——2025 真题的状态是 f[i][j][k](前 i 天、已淘汰 j 人、其中 k 人耐心值 ≤ j),因为"淘汰是否发生"取决于当前已淘汰人数。
  • 贡献延迟技巧:转移时某个选择的影响无法立刻确定(如"选了哪个数"),就先记数量、在约束触发的那一刻再结算——考场上称"提前/延迟钦定"。
  • 容斥:约束"每个 ≤ 上限"的计数 = 无约束方案 − 至少一个越界方案……,对 2^m 个越界集合做符号求和。
  • 取模纪律:998244353 / 1e9+7;减法后 +mod;读清"方案数"还是"期望"。

练习 P1450【HAOI2008】硬币购物(详细解答)

题意:4 种面值 c1..4 的硬币无限枚;q ≤ 1000 次询问,每次给 4 个使用上限 di 与金额 s ≤ 105,问恰好凑出 s 且第 i 种硬币用量 ≤ di 的方案数。

提示:4 个上限如果直接进状态是多少?容斥的"越界集合"是什么?
直接做是 O(s·d1·d2·d3·d4),爆炸。注意到上限只有 4 个:预处理"无上限"的完全背包 f[x];对每个询问,枚举哪几个硬币确定超限(选了 d_i+1 个的集合 S,先扣掉 (d_i+1)c_i 的金额),方案数符号为 (−1)^{|S|}——经典"至少型"容斥。每询问 O(16)。
详细解答(含推导与代码)

容斥推导:设 Ai = "第 i 种硬币用量 ≥ di+1" 的方案集合。答案 = |无任何 Ai| = ΣS⊆{1,2,3,4} (−1)|S| · g(s − Σi∈S(di+1)ci),其中 g(x) 为无上限时凑出 x 的方案数(完全背包预处理)。金额为负时该项为 0。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll f[100005]; int c[4], d[4];
int main() {
    for (int i = 0; i < 4; i++) scanf("%d", &c[i]);
    f[0] = 1;
    for (int i = 0; i < 4; i++)                 // 完全背包:无上限方案数
        for (int x = c[i]; x <= 100000; x++) f[x] += f[x - c[i]];
    int q; scanf("%d", &q);
    while (q--) {
        for (int i = 0; i < 4; i++) scanf("%d", &d[i]);
        ll s, ans = 0; scanf("%lld", &s);
        for (int S = 0; S < 16; S++) {          // 2^4 枚举越界集合
            ll t = s; int bits = 0;
            for (int i = 0; i < 4; i++)
                if (S >> i & 1) { t -= (ll)(d[i] + 1) * c[i]; bits++; }
            if (t >= 0) ans += (bits & 1 ? -1 : 1) * f[t];
        }
        printf("%lld\n", ans);
    }
    return 0;
}

走查一遍(c = [1,2,3,4],询问 d = [2,1,0,0],s = 4;预处理得 f[0]=1、f[1]=1、f[4]=5):

越界集合 S扣减金额 Σ(di+1)cif 值符号贡献
∅0f[4] = 5++5
{1}(硬币1 ≥ 3 枚)3×1 = 3f[1] = 1−−1(排除 {1,1,1,1})
{2}(硬币2 ≥ 2 枚)2×2 = 4f[0] = 1−−1(排除 {2,2})
{3}(硬币3 ≥ 1 枚)1×3 = 3f[1] = 1−−1(排除 {1,3})
{4}(硬币4 ≥ 1 枚)1×4 = 4f[0] = 1−−1(排除 {4})
其余集合金额为负,贡献 00
合计5 − 4 = 1(唯一合法方案 {1,1,2} ✓)

无上限的 5 种凑法逐一被 4 个"单条件越界"项排除——容斥就是这样"先全算、再逐个扣掉犯规的"。

易错点:① (d+1)·c 可能溢出 int → long long;② 符号 (−1)^{|S|} 别写反;③ f[t] 本身就是 ll,无需再取模(本题不取模,若考场题要求取模则全程取模)。本题是"容斥 + 多维约束计数"的最小完整模型,直接对标 2025 T4 的思维框架。

进阶对照(P7914 括号序列 · 要点):区间 DP 防重技巧——f[l][r](两端匹配)与 g[l][r](整体合法但不匹配),并列拼接时"规定最后一段是完整匹配段"来去重;转移枚举中间 * 段长度 ≤ k。

专题⑧ 扫描线与区间覆盖 押题 T2/T3 · ★★★★

大纲新增"扫描线"(7 级)|对标:2024 超速检测(区间选点贪心)|推荐练习:P5490、P1856

核心知识

  • 面积并扫描线:把每个矩形拆成"左边界 +1、右边界 −1"两个事件,按 x 排序;线段树维护 y 轴上"被完全覆盖的长度",相邻事件间面积 = 覆盖长度 × Δx。
  • 线段树节点不 pushdown:覆盖计数是"整段打标记 + 回溯时由子节点上拉",单点修改很少,是懒标记的变体——注意 cover 长度的更新公式:若整段被覆盖 len = 区间长;否则 len = 左子 len + 右子 len。
  • 离散化(大纲新增技巧):y 坐标先排序去重,线段树建在"坐标段"上而非数值点上。
  • 姐妹模型 · 区间选点(2024 超速检测):给定区间求最少选点使每区间含一个点——按右端点排序贪心,能复用就不加新点。

练习 P5490【模板】扫描线(详细解答)

题意:n ≤ 105 个矩形(坐标 ≤ 109),求面积并。

提示:线段树每个节点要存什么?为什么不需要懒标记下传?
每节点存 cnt(整段被覆盖的次数)与 len(该段被覆盖的长度)。修改永远是"给一个整段 ±1",从不查询单点,所以无需下传——查询时若 cnt>0 直接返回整段长,否则由子节点上拉。y 轴离散化后建树,树叶子对应"离散化后的第 i 段区间 [y[i], y[i+1])"。
详细解答(含代码)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 100005;
struct Node { int cnt; ll len; } t[8 * N];      // 离散化后最多 2n-1 ≈ 2e5 段 → 需 8e5 节点
struct Ev { ll x, y1, y2; int v; };              // v=+1 进 -1 出
vector<Ev> ev; vector<ll> ys;
void pushup(int u, int l, int r) {
    if (t[u].cnt) t[u].len = ys[r + 1] - ys[l];  // 整段被覆盖
    else t[u].len = (l == r) ? 0 : t[u*2].len + t[u*2+1].len;
}
void update(int u, int l, int r, int ql, int qr, int v) {
    if (qr < l || r < ql) return;
    if (ql <= l && r <= qr) { t[u].cnt += v; pushup(u, l, r); return; }
    int m = (l + r) / 2;
    update(u*2, l, m, ql, qr, v); update(u*2+1, m+1, r, ql, qr, v);
    pushup(u, l, r);
}
int main() {
    int n; scanf("%d", &n);
    for (int i = 0; i < n; i++) {
        ll x1, y1, x2, y2; scanf("%lld%lld%lld%lld", &x1, &y1, &x2, &y2);
        ev.push_back({x1, y1, y2, 1}); ev.push_back({x2, y1, y2, -1});
        ys.push_back(y1); ys.push_back(y2);
    }
    sort(ev.begin(), ev.end(), [](auto&a, auto&b){ return a.x < b.x; });
    sort(ys.begin(), ys.end()); ys.erase(unique(ys.begin(), ys.end()), ys.end());
    int m = ys.size() - 1;                       // m 段
    ll ans = 0, lastX = ev[0].x;
    for (auto &e : ev) {
        ans += t[1].len * (e.x - lastX);         // 上一状态覆盖了 (lastX, e.x)
        int l = lower_bound(ys.begin(), ys.end(), e.y1) - ys.begin();
        int r = lower_bound(ys.begin(), ys.end(), e.y2) - ys.begin() - 1;
        if (l <= r) update(1, 0, m - 1, l, r, e.v);
        lastX = e.x;
    }
    printf("%lld\n", ans);
    return 0;
}

走查一遍(矩形 A:x∈[0,3)、y∈[0,2);矩形 B:x∈[2,5)、y∈[1,3)):

事件 x事件内容本段贡献(旧覆盖长 × Δx)处理后覆盖段(长度)
0A 进入 y[0,2)—(起点)[0,2)(2)
2B 进入 y[1,3)2 × (2−0) = 4[0,3)(3)
3A 离开3 × (3−2) = 3[1,3)(2)
5B 离开2 × (5−3) = 4无(0)
合计4 + 3 + 4 = 11(验证:6 + 6 − 1 = 11 ✓)

x=2 处的贡献用的是 B 进入之前的覆盖长 2——"先算旧状态、再更新"的顺序一眼可见。

易错点:① 线段树叶子对应段 [ys[i], ys[i+1]),右边界查询要 −1;② 面积累加放在"处理事件前"用旧 len 乘 Δx;③ 坐标 1e9 必须 ll;④ 周长并(P1856)还需维护覆盖段数与左右端点,是本题的加练变式。

四、自测小测(15 题,即时判分)

五、考场策略与防爆零清单(点击打卡)

时间分配

时段动作
14:30–14:50通读 4 题,给每题标"暴力档/正解档",排做题顺序
14:50–16:00T1 + T2 拿满(目标 200)
16:00–17:10T3:先写暴力档,再冲正解
17:10–18:10T4:特殊性质档逐一落袋(通常可拿 20–40)
18:10–18:30全面检查:文件名 / freopen / 多组数据清空 / 删调试输出
最后提醒:2025 年中档得分约 120–140,意味着"T1T2 满分 + T3 大部分 + T4 部分分"就是有竞争力的答卷。押题押的是复习优先级,考场上遇到没见过的模型,回到"部分分阶梯"冷静拆解。

依据截至 2026-09 的公开资料整理 · 教材生成于 2026-09 · 祝考试顺利