Logo lxn 的博客

博客

标签
暂无

KOI2024R2

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-10-15 16:03:30

deepseek翻译官方题解

T2 P12648 [KOI 2024 Round 2] 路灯

“初等部第2题 / 中等部第1题. 路灯” 问题解答

作者:尹教俊

部分问题 1

从 x = 0 到 x = A₁ 的整数位置上的黑暗程度依次是 0, 1, ..., A₁。 此外,从 x = A₁ + 1 到 x = L 的整数位置上的黑暗程度依次是 1, 2, ..., L - A₁。

也就是说,由于排序后的黑暗程度值存在简单的规律,因此可以在比较 A₁ 和 L - A₁ 的大小后输出答案。

部分问题 2

根据题目中给出的公式,可以在 O(N) 时间内计算出一个位置的黑暗程度。

因此,计算所有位置的黑暗程度后进行排序,即可在 O(NL) 的时间复杂度内解决。

部分问题 3

路灯从 0 到 L 等间距分布。

因此,持续输出数字直到输出 K 个数为止:先输出 N 次 0,然后对于 1 及以上的整数,依次各输出 2(N - 1) 次即可。

部分问题 4

观察到以下情况:

  • 位置 0 ≤ x ≤ A₁ 的黑暗程度是 A₁ - x。
  • 位置 Aᵢ ≤ x ≤ Aᵢ₊₁ 的黑暗程度是 min{x - Aᵢ, Aᵢ₊₁ - x}。
  • 位置 A_N ≤ x ≤ L 的黑暗程度是 x - A_N。

对于每个区间,每个位置的黑暗程度可以在 O(1) 时间内计算出来。因此,可以在 O(L) 的时间复杂度内完成所有位置的黑暗程度计算及排序。

部分问题 5

以 N 个路灯的放置位置作为起点进行 BFS。

每当从队列中移除一个(位置,距离)对时,输出的距离值就是题目所要求的答案。

队列的插入和删除操作总共执行 O(N + K) 次。

在将点插入队列之前需要检查其是否已被访问过,这可以使用 std::set 等平衡二叉搜索树库来高效处理。

因此,总时间复杂度为 O((N + K)lg(N + K))。

也可以使用 O(N + K) 的线性时间和空间复杂度来解决此问题。

T3 P12649 [KOI 2024 Round 2] 收集彩球

“初等部第3题 / 中等部第2题. 收集颜色” 问题解答

作者:朴灿率

部分问题 1

由于 N ≤ 2,因此可以预先计算所有可能输入的答案并输出。

部分问题 2

由于 N ≤ 20,因此可以定义 dp[bit] 为状态为 bit 时从初始状态所需的最小移动次数,从而使用位运算的动态规划来解决问题。

将 bit 的第 i 位定义为:如果颜色为 i 的球在同一个箱子中则为 1,否则为 0。由于从每个状态能得出的当前球的箱子分配是唯一的,因此可以使用动态规划。

部分问题 5

设第 i 个箱子中下方球的颜色为 A_i,上方球的颜色为 B_i。考虑一个有 N 个顶点的有向图 G 和一个无向图 G'。对于所有的 A_i, B_i,在 G 中添加有向边 (A_i, B_i),在 G' 中添加无向边 (A_i, B_i)。如果 A_i = B_i,则在两个图中各添加一条指向自身的边。

在图 G' 中,所有顶点的度数恰好为 2,因此该图由简单环的集合构成。属于不同简单环的顶点互不影响,因此只需逐个处理图中的简单环即可。

如果这样构建的图 G' 中的每个环,在 G 中出度大于等于 2 的顶点不超过 1 个,则可以通过以下过程移动球,使得所有箱子中都装入相同颜色的球:

  • 任意选择一个出度小于等于 1 的顶点。
  • 将该顶点移动到空箱子中。
  • 现在重复以下过程,直到处理完一个环中的所有顶点: – 设刚才选择并移动的顶点为 u。 – 对于所有顶点 v,如果存在边 (v, u) 则将其移除。 – 任意选择一个在 G 中出度为 0 且在 G' 中度数小于等于 1 的顶点 w。 – 顶点 w 的出度为 0 意味着颜色为 w 的球都位于箱子的最顶部。 – 此外,度数为 1 意味着通过一次移动即可将与该颜色相同的球收集到一个箱子中。因此,选择颜色为 w 的两个球中的一个移动到另一个箱子,即可将球收集到一个箱子中。

然而,如果一个环中存在两个或更多出度为 2 的顶点,则无法将上述过程进行到底。否则,将一个环中包含的球移动至每个箱子都装有相同颜色球所需的移动次数如下:

  • 设环中包含的顶点数为 V。
  • 若 V = 1,则为 0 次。
  • 若 V > 1,则为 V + 1 次。

对所有环的上述次数求和,即可求得正确答案。

T4 P12652 [KOI 2024 Round 2] 拔树游戏

“中等部第4题 / 高等部第3题. 树提取” 问题解答

作者:李恩祖

部分问题 1

直接实现题目中给出的操作,可以在 O(N²) 的时间复杂度内解决问题。

部分问题 2

可以证明,在使用管理最小值、最大值的数据结构(如堆)的原理进行提取操作后,对于所有 2 ≤ i ≤ N,A_{P_i} < A_i 仍然成立。因此,根顶点始终是所有顶点中权重最小的。所以答案总是 1, 2, ..., N。

部分问题 3

假设某个顶点 u 有子节点 v₁, ..., v_k,不失一般性地,设 A_{v₁} < ... < A_{v_k}。如果 u 被选为特殊路径,那么具有最小权重的子节点 v₁ 也将被选为特殊路径。根据部分问题的条件,在提取操作之后,顶点 v₁ 的权重会变得更小,因此可知 v₁ 仍然会被选为特殊路径。也就是说,在以 v₁ 为根的子树中的所有顶点都被移除之前,v₁ 会一直被选为特殊路径。即,按照 v₁, ..., v_k 的顺序,这些顶点子树中的所有顶点将依次被选择。因此,从根顶点开始执行 DFS,并按照权重从小到大的顺序访问顶点,即可知 DFS 中的顶点访问顺序就是答案。

部分问题 4

如果只考虑度数为 3 及以上或者是叶子的顶点,可以创建一个压缩形式的树。这样创建的压缩树的边上将挂有一串顶点。对于压缩树中的每个顶点,使用优先队列来管理其相邻顶点的权重,而对于挂在压缩树边上的成串顶点,则使用队列进行管理。根据问题条件,压缩树的高度较小,因此可以在 O(20 log N) 的时间复杂度内找到特殊路径。沿着特殊路径向上移动,同时更新边的队列和顶点的优先队列,则提取操作也可以在 O(20 log N) 的时间复杂度内实现。总时间复杂度为 O(20N log N)。

部分问题 5

通过在子树中解决部分问题,并将其合并来解决父节点的问题。假设某个顶点 u 有子节点 v₁, ..., v_k,将以 v_i 为根的子树对应的答案序列记为 B_{v_i}[...]。这意味着当 v_i 包含在特殊路径中并执行提取操作时,顶点 v_i 的权重按照序列 B_{v_i}[...] 的顺序变化。我们必须利用此信息来找到以 u 为根的子树的答案序列 B_u[...]。显然,B_u[...] 的第一个数字是 A_u。现在,剩余的数字可以通过像执行归并排序一样,从 B_{v₁}[...], ..., B_{v_k}[...] 中选取最小的数字并添加到 B_u[...] 的末尾,重复此过程即可全部求出。

如果简单地实现此过程会导致时间超限,因此需要优化。在从序列 B_i[...] 的前面开始读取时,按照最大值更新的时刻将其划分为新的组的方式来管理组,并将组的代表值定义为该组最左边的数字。例如,序列 2, 1, 3, 5, 4, 8, 6, 7 被管理为 [2, 1], [3], [5, 4], [8, 6, 7]。这样划分组后,在将多个序列合并为一个时,同组的数字将始终保持在同一组中,并且是按照组的代表值顺序进行合并的。此外,代表值比第一个数字小的组将会合并成一个新的组。因此,使用优先队列来管理这些组,并运用"小集合合并到大集合"的技巧,可以在 O(N log² N) 的时间复杂度内解决问题。

再深入思考一下,可以归纳地证明,此解法最终所求的是在保持父-子顺序关系的排列中字典序最小的那个排列。因此,使用优先队列从根顶点开始选择权重最小的顶点,可以在 O(N log N) 的时间复杂度内解决问题。

T5 P12651 [KOI 2024 Round 2] 最大异或

“中等部第3题 / 高等部第2题. XOR最大值” 问题解答

作者:罗正辉

部分问题 1

由于 N ≤ 30,输入规模非常小,因此可以通过检查从 S 中选择 s₁ 和 s₂ 的所有 O(N⁴) 种情况来解决问题。

部分问题 2

介绍两种解法。

解法 1

众所周知,在由 N 个非负整数组成的数组 A 中,选择两个数使其 XOR 值最大化的问题,可以使用 Trie 数据结构在 O(N log(max Aᵢ)) 时间内解决。

本题是从 S 的 O(N²) 个子串中选择 2 个使其 XOR 值最大化的问题,由于每个子串的长度不超过 N,因此可以使用 Trie 在 O(N³) 时间内解决。

解法 2

不失一般性,假设 |s₁| ≥ |s₂|。同时,设 S 中最早出现的 '1' 的位置为 x(索引从 1 开始)。

观察 1: 只需考虑 s₁ 是 S 的前缀的情况。

证明: 假设存在一个答案,其中 s₁ 不是 S 的前缀。如果 s₁ 紧邻的前一个字符是 '1',则可以将 s₁ 向前扩展一位以增加其值,导致矛盾;如果 s₁ 紧邻的前一个字符是 '0',则向前扩展一位不会改变答案。因此,如果 s₁ 不是 S 的前缀,可以将其扩展到成为前缀为止。

观察 2: 两个子串 XOR 后,无法得到比从后往前数第 N - x + 1 位更高的位。

证明: 要得到第 t ≥ N - x + 2 位为 1,需要在 S[1···x-1] 之间存在 '1',但根据 x 的定义,在 S[x] 之前不可能存在 '1'。

观察 3: 只需考虑 s₁ 包含 S[x···N] 的形式。

证明: 需要证明如果 s₁ 不包含 S[x···N],则无法使第 N - x + 1 位为 1,证明过程与观察 2 类似。

综合以上三个观察,可以得到以下结论:

定理 1: 存在 s₁ = S 的答案。

证明: 根据观察 3,存在 s₁ 是 S 的后缀的答案。又根据观察 1,将 s₁ 向前扩展不会使答案减小。因此,存在 s₁ = S 的答案。

遍历 S 的所有子串 s₂,计算 S 与 s₂ 的 XOR 值并取最大值即可。这种情况下,可能的 s₂ 子串有 O(N²) 个,每次 XOR 操作需要 O(N) 时间。因此,可以在 O(N³) 时间内解决问题。

部分问题 3

介绍两种解法。

解法 1

使用 Trie 优化部分问题 2 的第二种解法,可以在 O(N²) 时间内解决。

解法 2

为方便起见,只考虑 S 中至少出现一个 '0' 和一个 '1' 的情况。如果 S 中没有 '1',则答案是 0;如果没有 '0',则答案是将 S 的最后一位改为 '0'。

设 S 中最早出现的 '1' 的位置以及连续的 '1' 的个数为 C,紧接着出现的 '0' 的位置为 P。

观察 4: 存在一个答案,其中 s₂ 的第一个字符与 S = s₁ 的第 P 个字符进行 XOR 运算。

证明: S[P] 是 '0',并且它的前面(S[P-1])是 '1',因此存在方法使得 XOR 结果的第 P 位为 '1',并且无法在不设置该位为 '1' 的情况下得到更好的答案。此外,容易知道,即使 s₂ 的第一个字符与比 S[P] 更靠前的字符匹配,答案也不会增加。

利用这个观察,可以将 s₂ 的候选数量减少到 C 个。例如,如果 S = 011110ABCDE...,那么 S 与 s₂ 的匹配方式只有以下四种:

S  = 011110ABCDE...
s2 =     11110A...
s2 =      1110AB...
s2 =       110ABC...
s2 =        10ABCD...

像这样,s₂ 的候选只有 C(≤ N) 个。实际需要考虑的 s₂ 只有 O(N) 种,因此可以在 O(N²) 时间内求得答案。

部分问题 4

将采用类似部分问题 3 的第二种解法的方式,即寻找与 S[P...N] 进行 XOR 后值最大的 s₂。

给定 A,最大化 A ⊙ B 等价于最小化 notA ⊙ B。寻找这样的 B 的问题,其答案是在所有可能的 B 的候选中,不大于 notA 的最大数,或者不小于 notA 的最小数。因此,如果能在 O(T(N)) 时间内比较子串的字典序,那么除了预处理时间外,可以在 O(N × T(N)) 时间内解决整个问题。

使用字符串 A#notA 的后缀数组,可以在 O(N log N) 时间内完成预处理,并在 O(1) 时间内比较子串的字典序;使用字符串哈希和二分查找,可以在 O(N) 时间内完成预处理,并在 O(log N) 时间内比较子串的字典序。两种方法的总时间复杂度都是 O(N log N)。

部分问题 5

将优化部分问题 3 的第二种解法。假设 S = 011110ABCDE...。

S  = 011110ABCDE...
s2 =     11110A...
s2 =      1110AB...
s2 =       110ABC...
s2 =        10ABCD...

如果 A = '1',则可以确定 s₂ = 10ABCD...;如果 A ≠ '1' 且 B = '1',则可以确定 s₂ = 110ABC...。

以此类推,设 P 之后首次出现的 '1' 的位置为 P + i,则可以确定 s₂ 是从 P - min(C, i) 位置开始的字符串。因此,可以在 O(N) 时间内找到作为答案的 s₂,从而在 O(N) 时间内解决整个问题。

CSP-J\/S近五年题型难度分析与备赛总结

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-10-24 09:23:10

CSP-J 2020-2024 题型难度分析

真题难度分布表

年份 题目 难度 算法
2020 优秀的拆分 入门 枚举
2020 直播获奖 普及 模拟、排序
2020 表达式 普及/提高 模拟、栈
2020 方格取数 普及/提高 动态规划DP
2021 分糖果 普及 数学
2021 插入排序 普及/提高 枚举、排序
2021 网络连接 普及/提高 模拟、字符串、STL
2021 小熊的果篮 普及/提高 模拟、STL、链表
2022 乘方 入门 枚举
2022 解密 普及 数学、二分
2022 逻辑表达式 普及/提高 模拟、栈、表达式求值
2022 上升点列 普及/提高 动态规划DP
2023 小苹果 普及 数学、找规律
2023 公路 普及 贪心、前缀和
2023 一元二次方程 普及/提高 模拟
2023 旅游巴士 普及/提高 图论、二分+BFS
2024 扑克牌 入门 模拟、STL
2024 地图探险 普及 模拟
2024 小木棍 普及/提高 动态规划DP、贪心
2024 接龙 提高/省选 动态规划DP、图论

CSP-S 2020-2024 题型难度分析

真题难度分布表

年份 题目 难度 算法
2020 儒略日 普及/提高 模拟、数学、二分
2020 动物园 普及/提高 数学、贪心、进制、位运算
2020 函数调用 提高/省选- 动态规划DP、拓扑排序
2020 贪吃蛇 NOI/NOI+/CTSC 贪心、队列、堆
2021 廊桥分配 普及/提高 模拟、前缀和、队列
2021 括号序列 提高/省选- 动态规划DP、区间DP
2021 回文 普及/提高 字符串、贪心
2021 交通规划 省选/NOI- 网络流、平面图
2022 假期计划 提高/省选- 广播、折半搜索
2022 策略游戏 普及+/提高 贪心、线段树、ST表
2022 星战 省选/NOI- 图论、哈希
2022 数据传输 省选/NOI- 动态规划DP、矩阵乘法
2023 密码锁 普及- 模拟、枚举
2023 消消乐 提高/省选- 动态规划DP、哈希
2023 结构体 提高/省选- 模拟
2023 种树 提高/省选- 贪心、二分
2024 决斗 普及- 贪心
2024 超速检测 普及+/提高 图论、最短路、次短路
2024 染色 提高/省选- 动态规划DP
2024 擂台游戏 NOI/NOI+/CTSC 贪心、递推、树形DP、差分

二、CSP-J 分析与备赛策略

总结与分析

  • 难度分布:以入门和普及-难度为主,每年会有1-2道普及+/提高甚至更高难度的题目用于区分层次。2024年出现了提高/省选难度的题目,说明难度有小幅提升趋势。

  • 算法考查:以模拟、枚举、数学(找规律、简单数论)、简单DP、基础数据结构(栈、链表、STL的简单应用)为主,注重对编程基础和逻辑思维的考查。

  • 趋势特点:题目越来越贴近实际应用场景,对代码的可读性和规范性要求有所提高;同时部分题目开始融合多个基础算法,考查综合运用能力(如2024年接龙结合了DP和图论)。

难度走势

  • 整体趋势:2020—2022 重基础+模拟+数学;2023—2024 出现明显算法进阶化(DP、图论、贪心占比上升)。

  • 题型分布

    • 模拟题:约占40%
    • 数学/找规律题:约占25%
    • 动态规划DP:2020, 2022, 2024(方格取数、小木棍、接龙等)
    • 模拟与枚举:每年都有(扑克牌、地图探险、直播获奖等)
    • 数学与规律:2021-2023(分糖果、解密、小苹果等)
    • STL与链表:2021, 2024(小熊的果篮、扑克牌)
    • 图论与BFS:2023, 2024(旅游巴士、接龙)

题型分布特点

  1. 第1题:多为入门难度,考查枚举、模拟、基础数学
  2. 第2题:普及到普及+,考查模拟、排序、简单贪心、数学
  3. 第3题:普及+/提高,常考动态规划、表达式求值、图论、数据结构
  4. 第4题:普及+/提高到提高/省选,考查较复杂的动态规划、图论、贪心等

常考知识点

  • 基础算法:枚举、模拟、排序
  • 数学:简单数论、找规律、基础组合
  • 数据结构:栈、队列、链表、STL应用
  • 动态规划:线性DP、状态机DP
  • 图论:BFS、最短路径
  • 其他:贪心、二分、前缀和

备赛策略

  1. 打好基础:熟练掌握C++语法和STL
  2. 刷题重点
    • 第1、2题:刷普及及以下难度的模拟、枚举、数学题
    • 第3题:重点练习动态规划、栈与队列、简单图论
    • 第4题:练习提高难度的DP、贪心、图论题
  3. 模拟实战训练:多做历年真题和模拟赛,适应比赛节奏
  4. 时间分配:前两题尽量快速AC,留足时间攻克后两题

备赛建议

阶段一(基础夯实)

  • 掌握:循环、数组、字符串、结构体
  • 刷题:洛谷普及-组、CSP-J历年T1-T2

阶段二(算法训练)

  • 专项:枚举+模拟+排序
  • 掌握:STL基础(vector, queue, map)

阶段三(进阶算法)

  • DP专题:路径型、序列型
  • 图论基础:BFS、DFS、最短路径(理解层面)

阶段四(冲刺实战)

  • 历年真题全模拟(限时4小时)
  • 调整策略:保证T1-T3全AC,T4拿部分分

三、CSP-S 分析与备赛策略

总结与分析

  • 难度分布:题目难度跨度大,从普及到NOI/CTSC级别均有涉及,每年都会有1-2道高难度(省选/NOI及以上)题目,同时也包含一定比例的普及+到提高级题目用于保底得分。

  • 算法考查:动态规划(DP)是绝对的核心考点,几乎每年都有多道题涉及;贪心、模拟、数学(数论、进制等)、图论(网络流、树链剖分等)、数据结构(线段树、ST表、堆、队列等)也是高频考点。

  • 趋势特点:高难度题目越来越注重算法的综合运用(如2024年擂台游戏融合了贪心、递推、树形DP、差分等多种算法),对选手的思维深度和代码实现能力要求较高。

难度走势

  • 总体趋势
    • 2020~2021:稳中求进(动态规划、贪心)
    • 2022起:算法难度显著提升(图论、网络流、矩阵快速幂)
    • 2023~2024:CSP-S 已与 NOI 难度接轨

重点算法分布

模块 出现频率 常考题型
动态规划DP ★★★★☆ 函数调用、数据传输、染色
贪心算法 ★★★☆☆ 动物园、策略游戏、决斗
图论算法 ★★★☆☆ 交通规划、星战、擂台游戏
字符串与哈希 ★★★☆☆ 消消乐、回文、表达式
数学/位运算 ★★★☆☆ 动物园、密码锁
搜索与组合 ★★★☆☆ 假期计划、策略游戏

难度层级

等级 占比 特征
普及/提高 35% 模拟、贪心、基础DP
提高+/省选 45% 状压DP、区间DP、搜索剪枝
NOI级 20% 树形DP、网络流、复杂图论

题型分布特点

  1. 第1题:普及-到普及+/提高,考查模拟、数学、枚举
  2. 第2题:普及+/提高到提高/省选,考查贪心、数据结构、DP
  3. 第3题:提高/省选,考查较复杂的DP、图论、数据结构
  4. 第4题:省选/NOI-到NOI/CTSC,考查高级算法与复杂问题建模

常考知识点

  • 数据结构:线段树、ST表、哈希、队列、堆
  • 动态规划:区间DP、树形DP、状态压缩DP、动态DP
  • 图论:最短路、网络流、拓扑排序、LCA
  • 数学:数论、组合数学、矩阵快速幂

备赛策略

  1. 系统学习

    • 掌握所有普及组知识点,并深入学习提高组内容
    • 重点学习动态规划、图论、数据结构
  2. 刷题方向

    • 第1题:保证稳定AC,练习模拟、数学、枚举
    • 第2题:练习贪心、数据结构、基础DP
    • 第3题:重点练习提高/省选-难度的DP、图论、数据结构
    • 第4题:尝试理解题解,学习高级算法
  3. 模拟赛与复盘

    • 定期参加模拟赛,严格计时
    • 每场赛后详细复盘,查漏补缺
  4. 时间管理

    • 前两题控制在1.5小时内完成
    • 留足时间思考第3题,第4题尽量拿部分分

备赛建议

基础巩固(算法巩固)

  • 系统掌握:DFS/BFS、二分、贪心、堆、并查集
  • 刷题范围:CSP-S T1、T2,普及+到提高组题库

进阶训练(核心算法)

  • 深入学习:动态规划(线性DP、区间DP、状态压缩DP)
  • 图论专题:最短路、拓扑排序、网络流、LCA
  • 数据结构:线段树、树状数组、ST表

冲刺阶段(综合实战)

  • 模拟全卷训练(限时4小时)
  • 分析历年CSP-S T3-T4思路
  • 以「优化DP + 贪心」为主要突破点

进阶拓展(面向NOI)

  • 学习:矩阵快速幂、树形DP、差分约束
  • 推荐OJ:洛谷提高组专题、AtCoder DP Contest、Codeforces比赛、CSP历年题库

山东CSP-S考前注意事项

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-10-24 09:32:57

试机注意事项

  1. 存储检查:先确认可以存储的盘符
  2. 文件保护:建立测试文件并重启,检查保护是否开放
  3. 编译测试:编写简单代码进行编译运行测试

考试前准备

试机结束至考试开始期间

  • 编译标准:C++14 标准下不能使用 gets(),读取带空格的字符串使用:

    getline(cin, s);  \/\/ 或
    fgets();
    
  • 编辑器设置

    • 开启自动保存选项
    • 修改编译参数: -O2 -std=c++14 -static -g -Wall -Wextra -Wl,--stack=536870912 占空间根据题目要求空间设置
    • 注意编译警告,确保函数有返回值
  • 代码验证:编译测试(考场未设置g++环境变量,熟练选手选做。)

    g++ -O2 -std=c++14 -Wall -Wl,--stack=1000000000 a.cpp -o a.exe
    

考试流程

文件操作规范

#include <cstdio>
using namespace std;

int main() {
    freopen("a.in", "r", stdin);
    freopen("a.out", "w", stdout);
    
    \/\/ 代码逻辑
    
    return 0;
}

重要提醒

  • freopen 放在流同步前面
  • 用了流同步,一定不要用scanf(),
  • cout不要用endl 要用"\n"
  • 使用流同步时不要用 fclose
  • 字符输出要仔细核对,使用写字板查看 .in 文件

读题审题策略

  1. 仔细阅读:不要节省读题时间,理解题意是得分基础
  2. 关注限制:注意时间、空间限制条件
  3. 细节把握
    • 注意上下界和特殊条件
    • 仔细阅读子任务数据范围
  4. 策略选择
    • 通读所有题目,选择可做题先做
    • 分析特殊数据,争取子任务分数
    • 避免死磕难题

考试技巧

心态与策略

信息奥赛考的是心态,打好暴力提升下限,沉稳分析提升上限!

  • 分任务拿分:不要死磕一个题目
  • 子程序设计:根据数据范围编写子程序,在主程序中灵活调用
  • 复杂度计算:分析数据范围,计算时空复杂度

数据分治技巧

\/\/ 示例:根据数据范围选择算法
if (n <= 1000) {
    \/\/ 使用暴力解法
    brute_force();
} else {
    \/\/ 使用优化算法
    optimized_solution();
}

常见问题与解决方案

初始化问题

  • 算法开始时进行必要的初始化
  • 将初始化代码作为算法的一部分编写

边界情况处理

  1. 整数溢出:中间过程可能爆 int/long long
  2. 数据范围:关注上下界和极限情况

多测试数据注意事项

\/\/ 清空数据结构
void clear_data() {
    \/\/ 使用 for 循环清空,避免 memset 误用
    for (int i = 0; i <= n; i++) {
        data[i] = 0;
    }
}

清空要点

  • 不确定时全部清空
  • 注意边界位置
  • 多测时必须读完所有输入

数组管理

const int MAXN = 100000 + 10;  \/\/ 多开一些空间

int arr[MAXN];  \/\/ 使用常量定义数组大小

空间计算

  • 线段树开4倍空间
  • 大数组要计算总空间
  • 动态开空间考虑极限情况
  • STL容器注意额外空间开销

溢出防护

\/\/ 加法和乘法检查溢出
long long result = (long long)a * b;  \/\/ 防止乘法溢出
int sum = a + b;  \/\/ 可能溢出,考虑用 long long

\/\/ 取模操作
result = (a * b) % MOD;  \/\/ 中间过程取模

变量管理

  • 易混淆变量使用明确命名
  • 使用注释标记重要变量
  • 注意运算符的优先级,多加括号。位移运算与算术运算。算术运算更高
  • 修改代码时检查所有相关位置

调试与验证

对拍策略

  1. 数据覆盖:测试上下界和极限数据
  2. 暴力验证:确保暴力解法正确性
  3. 减少重合:避免正解和暴力犯相同错误

RE问题排查

  1. STL安全

    if (!container.empty()) {
        value = container.front();  \/\/ 判空后访问
    }
    
  2. 迭代器安全:避免在 begin()--end()++

  3. 数据结构完整性:考虑空节点情况

  4. 清理调试代码:提交前移除所有调试语句

实用工具

文件比较工具

FC命令使用说明

# 基本用法
fc file1.out file2.out

# 忽略大小写
fc \/c file1.out file2.out

# 以ASCII方式比较
fc \/a file1.out file2.out

# 以二进制方式比较
fc \/b file1.out file2.out

# 显示不同行的行号
fc \/n file1.out file2.out

时间检测工具

#include <iostream>
#include <ctime>
#include <iomanip>
using namespace std;

#define time_now double(clock())\/CLOCKS_PER_SEC

int main() {
    double time = double(clock()) \/ CLOCKS_PER_SEC;
    while (1) {
        if (time_now - time > 0.8) break;
    }
    cout << fixed << setprecision(5) << time_now - time;
    return 0;
}

空间计算工具

#include <iostream>
using namespace std;

bool STSTST;
int a[114514];
int b[30][40000];
bool EDEDED;

int main() {
    cout << "USE " << (&EDEDED - &STSTST) \/ 1024.0 \/ 1024.0 << "MB" << endl;
    return 0;
}

考试结束前检查

  1. 最后15分钟:检查文件版本是否正确
  2. 文件操作:确认已取消调试用的文件输入输出注释
  3. 编译验证:修改文件后必须重新编译测试

祝各位考生在CSP2025中取得优异成绩!

题解:B4428 [CSP-X2025 山东] 勇者斗恶龙

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-11-10 17:05:45

题目分析

对序列 $a_1,a_2,...a_n$ 来说,只有连续的相等序列段才需要修改。

对于第 $i$ 个元素来说,只可能因为与左侧相等被修改一次,与右侧相等被修改一次,最多修改次数为两次。

因此,第 $i$ 个元素是否被修改只与第 $i-1$ 个元素相关,可以用动态规划解决该问题。

设 $dp_{i,0/1/2}$ 表示当前点被修改的次数。

当某个 $k$ 满足 $a_i+j \neq a_{i-1}+k$ 时,转移方程为 $dp_{i,j}=\min(dp_{i,j},dp_{i-1,k}+b_i\times j)$。

边界条件

我们要求最小值,所以 $dp$ 数组要初始化为最大值。

初始条件为 $dp_{0,0}=dp_{0,1}=dp_{0,2}=0$ .

参考代码

#include<bits\/stdc++.h>
using namespace std;
const int N=2e5+9;
typedef long long ll;
ll a[N],b[N],dp[N][3],n;\/\/对当前数来说f[i][0\/1]表示变了自己增加了多少,最多加2。 
int main(){
	cin>>n;
	for(int i=1;i<=n;i++)cin>>a[i]>>b[i];
	\/\/可以分类讨论,感觉不如dp好写,dp可以无脑暴力枚举 
	
	memset(dp,0x3f,sizeof(dp)) ;
	a[0]=-2e9;
	dp[0][0]=dp[0][1]=dp[0][2]=0;
	for(int i=1;i<=n;i++){
		for(int j=0;j<3;j++)
			for(int k=0;k<3;k++)
				if(a[i]+j!=a[i-1]+k) dp[i][j]=min(dp[i][j],dp[i-1][k]+j*b[i]);	
	} 
	cout<<min(dp[n][0],min(dp[n][1],dp[n][2]));
}

信息学开放性比赛汇总-持续更新中

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-12-26 11:20:51

信息学有很多开放性赛事。

1.atcoder ABC周赛

难度:入门-提高

网址:atcoder.jp

读英文题目困难的通过可以通过atcoder better插件翻译。atcoder better使用帮助

时间:一般在周六晚上20:00-21:40

讲评:潍坊一中团队周四晚上8:30-9:30讲题和答疑,#腾讯会议:659-8861-3002。 网络讲评:清北学堂在哔哩哔哩上每个周日讲评并有回放。清北学堂atcoder讲解

2.核桃周赛

时间:每个周末开放,从周四晚上到周日晚上。

网址:https://htoj.hetao101.com/

新手赛:适合入门选手

彩虹赛:适合入门-提高选手

讲评:赛后发布讲评链接

3.牛客周赛

时间:每个周日晚上19:00-21:00

网址:https://ac.nowcoder.com/

难度:入门-提高

4.洛谷网站比赛

网址:www.luogu.com.cn 不同难度有标注,根据自己的情况选择。

5.coderforces

网址:https://codeforces.com/ 俄罗斯算法竞赛网站,比赛时间一般在晚上22点之后,时间对我们不算友好。

6.信友队

网址:https://contest.xinyoudui.com/

每个月的中旬有基础组和普及组月赛。

7.梦熊

网址:https://mna.wang/

星航计划比赛,基础组,入门组,提高组依次进行。

欢迎大家留有补充比赛信息

P2513 [HAOI2009] 逆序对数列

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-01-16 09:22:53

题目分析

设$f[i][j]$为用了前i个数产生j个逆序对的方案数。

$ f[i][j]=\sum_{j-i+1}^k f[i-1][k] $ 这是一段连续的数值,可以用区间和优化。

参考代码

#include <iostream>
#include <cstdio>
using namespace std;
int n,k;
const int p=10000;
int f[2000][2000],s[2000][2000];
int main() {
    scanf("%d%d",&n,&k);
    f[1][0]=1;
    for(int i=0;i<=k;i++)s[1][i]=1;
    for(int i=2;i<=n;i++){
	
        for(int j=0;j<=k;j++) {
            if(j>=i)f[i][j]=(s[i-1][j]-s[i-1][j-i])%p;
            else f[i][j]=s[i-1][j];
       		if(j>=1)s[i][j]=(s[i][j-1]+f[i][j])%p;
       		else s[i][j]=f[i][j];
        }
      
   }

    printf("%d\n",(f[n][k]+p)%p);
    return 0;
}

滚动数组优化

本题的第$i$行只与$i-1$行相关。

参考代码

#include <iostream>
#include <cstdio>
using namespace std;
int n,k;
const int p=10000;
int f[2][2000],s[2][2000];
int main() {
    scanf("%d%d",&n,&k);
    f[1][0]=1;
    for(int i=0;i<=k;i++)s[1][i]=1;
    for(int i=2;i<=n;i++){
        for(int j=0;j<=k;j++) {
            if(j>=i)f[i&1][j]=(s[(i-1)&1][j]-s[(i-1)&1][j-i])%p;
            else f[i&1][j]=s[(i-1)&1][j];
       		if(j>=1)s[i&1][j]=(s[i&1][j-1]+f[i&1][j])%p;
       		else s[i&1][j]=f[i&1][j];
         
        }
      
	}

    printf("%d\n",(f[n&1][k]+p)%p);
    return 0;
}

20250117-公益AB班测试

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-01-17 14:27:50

T14281 单栈排序

题意简述

给定一个栈和一个 n 的全排列作为入栈序列,试通过调整出栈次序,得到字典序最大的 出栈序列。

解法一

暴力枚举每次是入栈还是出栈,即穷举所有可能的出栈序列,从而得到字典序最大的出栈序列。 时间复杂度:$O(C(n))$,其中 $C(n)$ 为卡特兰数列的第 $n $项,期望得分 $40 $ 分。

解法二

使用各种神奇的贪心算法,求解字典序最大的出栈序列。由算法复杂度及正确性决定最后 得分。 期望得分 0−100 分。

解法三

依旧考虑贪心算法。要求字典序最大,故我们可以从 $n ... 1$ 贪心地试探每个数能否加入 出栈序列。 假设我们已经枚举到了 $i$,若栈顶元素比 $i$ 大,我们则可以弹出栈顶元素,将其加 入出栈序列,直到栈顶元素≤ i。 若 $i$ 未在栈中,我们则将入栈序列中在 $i$ 前面且未入栈的数入栈,并将 $i$ 加入出栈序列; 若 $i$ 在栈中且是栈顶元素,我们则将 $i$ 弹出栈,并加入出栈序列; 若 $i$ 已经在栈中且不是栈顶元素,则说明若将 $i$ 加入出栈序列,之前必然要把比 $i$ 小的数 弹出栈,我们不这样做,继续枚举 $i−1$。 注意由于涉及大规模文件处理,此题需要使用输入输出优化。 时间复杂度为$ O(n)$,期望得分 $100$ 分。

参考代码

#include <cstdio>
#include <cstdlib>
#include <iostream>
#define N 1111111
using namespace std;
int stack[N], a[N];
bool in_stack[N], is_print[N];
int getint()
{
	char ch;
	do
	{
		ch=getchar();
	}while (ch!='-'&&(ch<'0'||ch>'9'));
	int ans=0,f=0;
	if (ch=='-') f=1; else ans=ch-'0';
	while (isdigit(ch=getchar())) ans=ans*10+ch-'0';
	if (f) ans*=-1;
	return ans;
}


int main() {

	int n = getint();
	for (int i = 1; i <= n; i++) a[i] = getint();
	int top = 0, pos = 0;
	for (int i = n; i >= 1; i--) {
		if (is_print[i]) continue;
		if (in_stack[i] && stack[top] != i) continue;
		while (top > 0 && stack[top] > i) {
			int t = stack[top]; top--;
			in_stack[t] = false;
			is_print[t] = true;
			cout<<t<<" ";
		}
		while(!in_stack[i]) {
			stack[++top] = a[++pos];
			in_stack[a[pos]] = true;
		}
		top--;
		in_stack[i] = false;
		is_print[i] = true;
		cout<<i<<" ";;
	}
	while (top > 0) {
		int t = stack[top]; top--;
		cout<<t<<" ";
	}
	return 0;
}

T14211 勤劳的蜜蜂

题目分析

每只蜜蜂的时间为$x+y$, $x+y+z$,...$x+y+iz$,得到一个等差数列求和的公式。 $$ \sum_{i=0}^j(x+y+iz)\leq t\ z \times i^2 +(2x+2y+z) \times i -2t \leq 0

$$ 求出符合要求的最大整数 $j$。

得到完整的周期后,再计算不完整周期中有多少工作时间。

参考代码

#include <cmath>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
using namespace std;
#define ll long long

const int MAXN=100000+2;
int N;
ll Ans,l,a,t,x[MAXN],y[MAXN],z[MAXN];

double Calc(double x,double y,double z,double t){
    if(z==0) return floor(t\/(x+y));
       
    z\/=2;
    double a=z,b=x+y-z,c=-t;
    double d=b*b-4*a*c;
    return floor((-b+sqrt(d))\/(2*a));
}

int main(){
    cin >> N >> t;
    for(int i=1;i<=N;i++) scanf("%lld %lld %lld",x+i,y+i,z+i);
    
    for(int i=1;i<=N;i++){
        ll n=(ll)Calc((double)x[i],(double)y[i],(double)z[i],(double)t);
        a=y[i]*n+z[i]*(n-1)*n\/2,l=t-a-(n+1)*x[i],Ans+=a;
        if(l>0) Ans+=l;
\/\/cout << n << " " << a << endl;
    }
    cout << Ans << endl;
    
    return 0;
}

T14938 展示牌

题目分析

如果先去掉旋转限制,本题方法是枚举最大高度H,贪心分类讨论:

贪心策略:

在高度不超过限制的情况下,使宽度增加尽可能少。 设h,w分别为当前人的高和宽,那么:

(1)h>H:

  • w>H:旋转还是会超出高度,不合法直接跳出。
  • w<=H:旋转

(2)h<=H:

  • h>=w:站着(这样宽度增加得少)
  • h<w:旋转,但是由于旋转的人数有限制,所以有一部分还是要站着。 因此特殊处理这种需要决策的情况(在h<=H&&h<w的这种情况下):问题:这些人站着躺着的高度都不会超出H,因此考虑怎样选择使得总宽度最少。

解决方案:

先让每个人都站着,此时总宽度设为W,然后再让n/2个人旋转:每个人旋转,那么这个人对W将加上值(h-w) (意思是将宽换成高的长),那么我们希望(h-w)尽量小,因此从小到大排序然后取前n/2个躺着就可以了。

参考代码

#include <bits\/stdc++.h>
using namespace std;
set<int> ::iterator sit;
int ans,sum,p[1005],i,a[1005],b[1005],cnt,CNT,j,ANS,n;
int cmp(int i,int j) {return i>j;}
bool FLAG;
int main()
{
    ANS=1000000000;
    scanf("%d",&n);
    for (i=1; i<=n; i++)
      scanf("%d%d",&a[i],&b[i]);
    for (i=1; i<=1000; i++)
    {
        sum=0; FLAG=true; cnt=0; CNT=0;
        for (j=1; j<=n; j++)
          if (b[j]<=i && (a[j]<=b[j] || a[j]>i)) sum+=a[j]; else
            if (a[j]>i && b[j]>i) {FLAG=false; break;} else
              if (b[j]>i) {cnt++; sum+=b[j];} else
              {
                  p[++CNT]=a[j]-b[j];
                  sum+=a[j];
              }
        if (!FLAG) continue;
        if (cnt>n\/2) continue;
        sort(p+1,p+CNT+1,cmp);
        for (j=1; j<=min(n\/2-cnt,CNT); j++) sum-=p[j];
        ANS=min(ANS,sum*i);
    }
    cout<<ANS;
    return 0;
}

T564027 幸运数

题目分析

本题是前面 P4446 [AHOI2018初中组] 根式化简 的一个变形题目。

两种形式可以归结为一种,因为$x,y >0$

  • 形式1:$y^1,y^2$都是偶数
    $x^2y^2 =(xy)^2 $ 找出$10^9$范围内$xy$,在$40000$范围内筛质数即可.
  • 形式2::$y^1,y^2$有一个奇数,

例如 $x^4y^9=(x^2y^3)^2*y^3 $

  • 形式3:y1y2都是奇数

    $$ x^5y^9\ =x^5y5y4\ =(xy)^5(y^2)^2\ =(xyy^2)^2*(xy)^3 $$

    可以转化为平方数$x^2y^2$或者$x^2y^3$形式。

    long long 范围内质因子的个数不超过$235*7...$不会超过20个质因子。

    因为包含因子1,所有的形式可以汇总为平方数,立方数和$x^2y^3$形式的数

  • 平方数 立方数可以直接通过pow判断

$x^2y^3 <=10^{18} < min(x,y)^5 $ 最大到$4000^5 =1024*10^15=10^{18}$ 筛出4000以内的质数

参考代码


#include <bits\/stdc++.h>
using namespace std;
typedef long long ll; 

const int N = 40000 + 10;
int T, len;
ll n, p[N];
bool isp[N];

void getprime(int n){
	for(int i=2;i<=n;i++){
		if(!isp[i])
			p[++len] = i;
		for(int j=1;j<=len&&i*p[j]<=n;j++){
			isp[i * p[j]] = 1;
			if(i % p[j] == 0)
				break;
		}
	}
	return;
}

bool pow3(ll m){
	ll t3=pow(m,1.0\/3);
	for(ll i=t3-1;i<=t3+1;i++)
		if(i*i*i==m)return true;
	return false;
}
bool pow2(ll m){
	ll t3=pow(m,1.0\/2);
	for(ll i=t3-1;i<=t3+1;i++)
		if(i*i==m)return true;
	return false;
}

string work(){
	cin>>n;
	ll m = n;
	if(pow2(n))return "yes";	
	if(pow3(n))return "yes";
	ll lim = ceil(pow(m, 0.2)) + 2;\/\/再找x^2,y^3 
	for(int i=1;i<=len&&p[i]<=lim;i++){
		ll pp = p[i] * p[i];
		if(m % pp == 0){
			m \/= pp;
			while(m % p[i] == 0)
				m \/= p[i];
		}
	}
	if(pow2(m)||pow3(m))return  "yes";
	return "no";
}

int main(){
	cin>>T;
	getprime(N - 10);
	while(T--)
		cout<<work()<<"\n";
	return 0;
}

T14212 烟花-题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-01-17 16:20:12

本题是树上的修改。修改次数$m$较大。

方法一:离线统计

每次修改,修改自己和关联节点。 每次统计能观看到烟花的素数加入计数。如果$u$节点观看次数为$cnt$,那么这个节点对答案的贡献为$\sum_{i=1}^{cnt}i=cnt*(cnt+1)/2$.

现在只需要统计每个节点的观看次数。用一个桶来统计节点的燃放次数。$o(m)$离线后,遍历每个节点,统计节点及其关联节点的观看次数。

时间复杂度 $o(m+n)$.

#define D(x) cout<<#x<<" = "<<x<<"  "
#define E cout<<endl
using namespace std;
typedef long long ll;
const int N = 100005;
int n,m,cnt[N]; ll num[N],ans;
struct Edge{ int to,nxt; }e[N<<1]; int head[N], ec;
void add(int a,int b){ e[++ec].to=b; e[ec].nxt=head[a]; head[a]=ec; }

int main(){
	read(n); read(m); int x;
	for(int i=2;i<=n;i++){
		read(x); add(x,i); add(i,x);
	}
	for(int i=1;i<=m;i++){
		read(x); cnt[x]++;\/\/离线对节点x的修改次数
	}
	for(int u=1;u<=n;u++){
		num[u]+=cnt[u];\/\/他自己燃放观看烟花的次数
		for(int i=head[u];i;i=e[i].nxt){\/\/他作为观众观看燃放烟花的次数
			num[e[i].to]+=cnt[u];
		}
	}
	for(int i=1;i<=n;i++){\/\/离线统计总次数。
		ans+=num[i]*(num[i]+1)\/2;
	}
	printf("%lld\n",ans);
}

方法二 在线统计

每次修改一个点时,会被更新的是它自己、它父亲、它儿子,如果我们用一个数组来存每个点周围的个点的和,那么一个点的修改造成影响的除了自己、父亲、儿子之外还有“祖父”,“孙子”,“兄弟”——对于它自己,修改了多少个点就需要加几;对于父亲和儿子,相邻点权值和+2;对于祖父、孙子、兄弟,相邻点权值和+1。

对每个点维护三个标记:

f1[x] 点x自己+1的次数

f2[x] x的儿子+1的次数

f3[x] x的孙子+1的次数

显然每次修改x之后总数为:

父亲节点fa[x]的值为:它自己加一次数+它儿子加一的次数+它的父亲加一的次数。

它自己x的值:它自己加一次数+它儿子加一的次数+它的父亲加一的次数。

它孩子yi的贡献:孩子个数*它加一的次数+它儿子加一的次数+它孙子+1的次数。

我们在每次修改的时候网上维护到“祖父”即可把f1,f2,f3维护出来。


#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long ll;
const int N = 1e5+10, M = 1e7+10,mod = 19260817;
int n,m;
ll ans = 0;
int f1[N],f2[N],f3[N],child[N],fa[N];
void update(int x){
    f1[x]++;
    if(x!=1) f2[fa[x]]++;
    if(fa[x]!=1) f3[fa[fa[x]]]++;
}
ll cal(int x){
    ll ans1 = 0;
    ans1 += (ll)f1[x] + f1[fa[x]] + f2[x];
    ans1 += (ll)f1[fa[x]]+f2[fa[x]]+f1[fa[fa[x]]];
    ans1 += (ll)f1[x]*child[x] + f2[x] +f3[x];
    return ans1;
} 
int main(){
    scanf("%d %d",&n,&m);
    int u;
    for(int i=1;i<=n-1;i++){
        scanf("%d",&u);
        fa[i+1] = u;
        child[u]++;
    }
    for(int i=1;i<=m;i++){
        scanf("%d",&u);
        update(u);
        ll res = cal(u);
        ans = (ans+ res) ;
    }
    cout<<ans;
    return 0;
}

# ST表(Sparse Table)教学设计

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-02-08 08:08:27

一、引入:区间最值问题

问题场景:给定长度为n的静态数组,q次询问区间[l, r]的最大值/最小值
暴力解法:每次遍历区间 → O(nq)
优化需求:需要O(nlogn)预处理 + O(1)查询的算法


二、ST表核心原理

1. 核心思想

  • 预处理:存储所有长度为2^j的区间最值
  • 查询:将任意区间分解为两个预处理的区间

2. 数学基础

可重复贡献性质
max(a,b,c) = max( max(a,b), max(b,c) )

3. 关键操作

  • 预处理
    st[i][j] = max(st[i][j-1], st[i+(1<<(j-1))][j-1])
    
  • 查询
    int k = log2(r-l+1);
    return max(st[l][k], st[r-(1<<k)+1][k]);
    

三、算法实现模板

int st[MAXN][21], Log2[MAXN];

void init() {
    Log2[1] = 0;
    for(int i=2;i<=n;++i) 
        Log2[i] = Log2[i\/2]+1;
    
    for(int j=1;j<=20;++j)
        for(int i=1;i+(1<<j)-1<=n;++i)
            st[i][j] = max(st[i][j-1], st[i+(1<<(j-1))][j-1]);
}

int query(int l, int r) {
    int k = Log2[r-l+1];
    return max(st[l][k], st[r-(1<<k)+1][k]);
}

四、复杂度分析

  • 预处理:O(nlogn)
  • 单次查询:O(1)
  • 空间:O(nlogn)

五、经典例题精讲

例题1:洛谷P3865 【模板】ST表

题目:静态区间最大值
代码要点

#include<bits\/stdc++.h>
using namespace std;
const int MAXN=1e5+5;
int st[MAXN][21], Log2[MAXN];

int main(){
    int n,m; scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i) scanf("%d",&st[i][0]);
    
    \/\/ 预处理Log2
    Log2[1]=0;
    for(int i=2;i<=n;++i) Log2[i]=Log2[i\/2]+1;
    
    \/\/ 构建ST表
    for(int j=1;j<=20;++j)
        for(int i=1;i+(1<<j)-1<=n;++i)
            st[i][j]=max(st[i][j-1],st[i+(1<<(j-1))][j-1]);
    
    \/\/ 处理查询
    while(m--){
        int l,r; scanf("%d%d",&l,&r);
        int k=Log2[r-l+1];
        printf("%d\n",max(st[l][k],st[r-(1<<k)+1][k]));
    }
    return 0;
}

例题2:CF1547F Array Stabilization (GCD version)

题意:环形数组,求最小操作次数使所有元素相等
ST表应用:预处理区间GCD
关键代码

int query_gcd(int l, int r) {
    int k = Log2[r-l+1];
    return gcd(st[l][k], st[r-(1<<k)+1][k]);
}

六、进阶练习题

题目来源 题号 名称 考察点
洛谷 P3865 ST表模板 基础应用
HDU 1756 Cupid's Arrow 二维ST表
Codeforces 1549D Integers Have Friends 区间GCD+ST表
AtCoder abc279F BOX 带权ST表

七、常见问题总结

  1. 区间长度处理:注意r-(1<<k)+1可能越界
  2. log2优化:预处理Log2数组比库函数更快
  3. 适用场景:仅限静态数据,动态更新需用线段树

八、扩展思考

  1. 二维ST表:处理矩阵最值查询
  2. 混合运算:结合GCD、位运算等性质
  3. 离线处理:结合Mo's Algorithm优化多查询

通过本教程的学习,结合配套的例题代码和练习题,能够快速掌握ST表的核心原理与实战应用。建议完成所有例题后,尝试独立完成练习题,并参考官方题解进行优化。

STL-vector deepseek学案(1)

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-05-08 20:20:53

STL-vector

一、vector基础知识

1. vector特性

  • 动态数组容器,可自动管理内存
  • 支持O(1)时间的随机访问
  • 尾部插入/删除效率高(O(1)),中间操作效率低(O(n))
  • 支持动态扩容(容量不足时自动翻倍)

2. 基本操作

#include <vector>
using namespace std;

\/\/ 声明与初始化
vector<int> v1;                 \/\/ 空vector
vector<int> v2(5);              \/\/ 5个0
vector<int> v3(5, 10);          \/\/ 5个10
vector<int> v4 = {1,3,5,7,9};   \/\/ 初始化列表

\/\/ 常用操作
v.push_back(x);       \/\/ 尾部插入元素
v.pop_back();         \/\/ 删除尾部元素
v.size();             \/\/ 获取元素个数
v.empty();            \/\/ 判断是否为空
v.clear();            \/\/ 清空元素
v.front();            \/\/ 第一个元素
v.back();             \/\/ 最后一个元素
v.begin(); v.end();   \/\/ 迭代器

二、一维vector应用

例题1:洛谷P3156 【深基15.例1】询问学号

题目描述:给定n个学生的学号,进行m次查询,每次给出位置k,输出对应学号

代码实现

#include <iostream>
#include <vector>
using namespace std;

int main() {
    int n, m, tmp;
    cin >> n >> m;
    vector<int> stu(n+1);  \/\/ 学号从1开始存
    
    for(int i=1; i<=n; ++i)
        cin >> stu[i];
    
    while(m--){
        int k;
        cin >> k;
        cout << stu[k] << endl;
    }
    return 0;
}

例题2:AtCoder ABC323 B - Round-Robin Tournament

题目描述:根据比赛结果统计每个选手的胜利次数并排序

核心代码

struct Player {
    int id, wins;
};

vector<Player> players(n);
\/\/ ...输入处理...
sort(players.begin(), players.end(), [](const Player& a, const Player& b) {
    return a.wins != b.wins ? a.wins > b.wins : a.id < b.id;
});

三、二维vector应用

1. 二维声明方式

vector<vector<int>> matrix;          \/\/ 空二维数组
vector<vector<int>> mat(5, vector<int>(3));  \/\/ 5行3列
vector<vector<int>> tri(5);          \/\/ 每行长度不同

例题3:洛谷P3613 【深基15.例2】寄包柜

题目描述:模拟超市寄包柜操作,支持:

  1. 在第i个柜子的第j个格子存入物品k
  2. 查询第i个柜子第j个格子的物品

代码实现

#include <iostream>
#include <vector>
using namespace std;

vector<vector<int>> lockers;  \/\/ lockers[i]表示第i个柜子

int main() {
    int n, q;
    cin >> n >> q;
    lockers.resize(n+1);  \/\/ 柜子编号从1开始
    
    while(q--){
        int op, i, j;
        cin >> op >> i >> j;
        if(op == 1){
            int k;
            cin >> k;
            if(lockers[i].size() <= j)  \/\/ 需要扩容
                lockers[i].resize(j+1);
            lockers[i][j] = k;
        } else {
            cout << lockers[i][j] << endl;
        }
    }
    return 0;
}

例题4:图的邻接表存储(Codeforces 231D)

题目描述:实现图的邻接表存储,处理多次查询

邻接表实现

int n = 1e5;
vector<vector<int>> adj(n+1);  \/\/ adj[u]存储u的邻接点

\/\/ 添加边u-v
adj[u].push_back(v);
adj[v].push_back(u);  \/\/ 无向图

\/\/ 遍历u的邻接点
for(int v : adj[u]){
    \/\/ 处理邻接点v
}

四、练习题推荐

  1. 洛谷P1177 【模板】排序(vector排序应用)
  2. Codeforces 474B - Worms(前缀和+二分查找)
  3. AtCoder ABC142D - Disjoint Set of Common Divisors(因数分解)
  4. at_abc404_c (图的存储与遍历)
  5. 洛谷P3383 【模板】线性筛素数(vector维护质数表)
  6. Codeforces 510B - Fox And Two Dots(二维矩阵DFS)

五、使用技巧

  1. 预分配空间:v.reserve(n)减少扩容次数
  2. 避免越界访问:使用v.at(i)进行边界检查
  3. 高效清空:vector<int>().swap(v)释放内存
  4. 二维数组初始化:vector<vector<int>>(n, vector<int>(m, -1))
  5. 搭配算法库:sort, lower_bound, unique等STL算法

六、常见错误

  1. 未初始化直接访问元素
  2. 在循环中使用size()但修改容器
  3. 二维数组行列顺序混淆
  4. 未处理扩容导致越界
  5. 错误估计时间复杂度(特别是中间插入操作)
共 90 篇博客