Codeforces EDU 与 AtCoder Library

共 19 题
📑 题目列表 19 题
#
★★★

1. 二分答案(binary search on answer)的判定函数设计中如何证明可行性单调,为什么最小化最大值与最大化最小值都能二分?

请说明二分答案(binary search on answer)的判定函数设计,如何证明可行性单调,以及为什么最小化最大值与最大化最小值都能二分?

  • 把最优值问题转化为"给定阈值是否可行"的判定问题
  • 可行性关于阈值的单调性(单调递增或递减)
  • 最小化最大值(二分上界)与最大化最小值(二分下界)的统一框架

二分答案的核心是:将"求最优值"转化为"对某个答案 x 判断是否存在满足约束的方案",即设计判定函数 ok(x)。可行性的关键性质是单调性:若 x 可行,则对"最小化最大值"问题,更大的 x 也一定可行(放宽约束);对"最大化最小值"问题,若 x 可行则更小的 x 也可行。正是这种单调性使候选答案区间可二分。最小化最大值(如把所有数分成若干段使每段和的最大值最小)二分的是"最大值上界",单调递增可行;最大化最小值(如把牛放到牛棚使最近距离最大)二分的是"最小值下界",单调递减可行。判定函数需在 O(判定复杂度) 内验证单调性,总复杂度 O(判定*log(值域))。

证明单调性通常是"可行性"的传递性:约束越宽松越容易满足。二分答案的价值是把"优化"变成"判定",只要判定函数易写,就能用二分把指数/多项式搜索降到 log 倍。关键在于边界写法(何时取 mid、何时收缩)与判定函数严格正确。

// 最小化最大值:分 m 段,每段和最大值最小
boolean ok(long x, int[] a, int m) {
    long sum = 0; int cnt = 1;
    for (int v : a) {
        if (sum + v > x) { cnt++; sum = v; } else sum += v;
    }
    return cnt <= m;
}
long bs(int[] a, int m) {
    long lo = 0, hi = (long)2e18;
    while (lo < hi) {
        long mid = (lo + hi) >>> 1;
        if (ok(mid, a, m)) hi = mid; else lo = mid + 1;
    }
    return lo;
}
#
★★★

2. 二分查找的边界变体中 lower_bound/upper_bound、第一个与最后一个满足条件元素及旋转数组的写法差异

请说明二分查找的各种边界变体:lower_bound/upper_bound、第一个/最后一个满足条件的元素,以及旋转数组查找的写法差异?

  • lower_bound 返回第一个 >= x 的位置,upper_bound 返回第一个 > x 的位置
  • 查找"第一个满足"与"最后一个满足"的模板差异
  • 旋转数组(部分有序)的二分

标准二分有多个变体:lower_bound 找第一个 >= x 的位置,upper_bound 找第一个 > x 的位置;两者区别在于相等时收缩方向不同。推广后,"找第一个满足条件 P 的位置"用模板:lo 指向可能答案区间的左边界,hi 右边界,当 P(mid) 成立时收缩右边界 hi=mid,否则 lo=mid+1,最终 lo 是第一个满足点;"找最后一个满足"则相反,收缩左边界。旋转数组(如 [4,5,6,1,2,3])查找需先判断 mid 落在哪一段有序区间,再决定收缩方向,同时处理重复元素时的边界情况。写法差异本质是"不变量"(invariant)设计不同:是保持 [lo,hi) 还是 [lo,hi] 闭区间,以及收缩时是否跳过 mid。

二分变体的核心是"不变量":循环期间保持某种性质(如 lo 左侧一定不满足、hi 右侧一定满足),终态即可确定答案。写对的关键是明确 mid 的归属(是否包含在候选区间)与相应的 +1/-1 调整。旋转数组则是利用"整体非单调但分段有序"的性质。

// lower_bound: 第一个 >= x
int lowerBound(int[] a, int x) {
    int lo = 0, hi = a.length; // [lo,hi)
    while (lo < hi) {
        int mid = (lo + hi) >>> 1;
        if (a[mid] >= x) hi = mid; else lo = mid + 1;
    }
    return lo;
}
// 旋转数组查找无重复:找到有序段再二分
int searchRotated(int[] a, int x) {
    int lo = 0, hi = a.length - 1;
    while (lo <= hi) {
        int mid = (lo + hi) >>> 1;
        if (a[mid] == x) return mid;
        if (a[lo] <= a[mid]) { // 左半有序
            if (a[lo] <= x && x < a[mid]) hi = mid - 1; else lo = mid + 1;
        } else { // 右半有序
            if (a[mid] < x && x <= a[hi]) lo = mid + 1; else hi = mid - 1;
        }
    }
    return -1;
}
#
★★

3. CSES Problem Set Range Query 章节在稀疏表与树状数组与线段树的工程实现。

请说明 CSES Range Query 章节中稀疏表、树状数组与线段树的工程实现与适用场景?

  • 稀疏表:离线、不可变、O(1) 查询、静态区间最值
  • 树状数组:单点更新 + 前缀查询,位运算高效
  • 线段树:区间更新 + 区间查询,支持懒标记

CSES Range Query 章节覆盖三类经典结构:稀疏表(Sparse Table)预处理 O(n log n)、查询 O(1),适合静态数组的区间最值(幂等操作如 max/min/gcd),但不能更新;树状数组(Fenwick/BIT)用 lowbit 维护前缀和,单点更新 O(log n)、前缀查询 O(log n),常数小、实现紧凑,适合可逆操作(求和、异或);线段树把区间递归分成节点,支持区间更新与区间查询 O(log n),配合懒标记可处理区间加/赋值等非幂等操作,通用性最强。工程选择:静态最值用稀疏表,单点更新+前缀查询用 BIT,区间更新+区间查询用线段树。

三者覆盖"静态 O(1) 查询"、"前缀可逆可更新"、"区间可更新可查询"三类需求。复杂度的差异来自信息冗余程度:稀疏表冗余最多但查询最快,线段树需 log 但最通用。理解"信息是否可合并、是否可更新"决定选型。

#
★★

4. 二分答案与倍增模板中如何用二进制拆分处理区间查询与在线问题?

请说明二分答案与倍增(binary lifting)模板如何用二进制拆分处理区间查询与在线问题?

  • 倍增用二进制拆分预处理步长跳跃,支持在线查询
  • LCA、树上路径、稀疏表都源自倍增思想
  • 与二分答案的互补:二分答案离线判定,倍增在线快速跳

倍增(binary lifting)的核心是"二进制拆分":把"跳 k 步"分解成若干 2 的幂次跳跃,预处理 up[x][i] 表示从 x 跳 2^i 步到达的位置,查询时从大到小拆解 k 完成 O(log n) 跳跃。典型应用是 LCA(倍增跳祖先)、树上第 k 祖先、稀疏表(区间长度按 2 的幂覆盖)、以及"在线静态"的区间查询。与二分答案不同:二分答案对"答案值"做二分并反复判定,通常离线;倍增对"步长"做二进制分解,适合在线、单次 O(log n) 的查询。二者互补:倍增处理"知道要跳多少步怎么跳",二分答案处理"最优值是多少"。

二进制拆分是共同思想:任何步数 k 都能写作若干 2 的幂之和,因此 O(log k) 次跳跃即可。倍增表是一种"稀疏表式"的预处理,用空间换时间。理解"跳 2^i 由两个 2^(i-1) 组合"是递归本质。

// 倍增:求第 k 个祖先
class BinaryLifting {
    int LOG; int[][] up;
    BinaryLifting(int[] parent, int n) {
        LOG = 20; up = new int[n][LOG];
        for (int i = 0; i < n; i++) up[i][0] = parent[i];
        for (int j = 1; j < LOG; j++)
            for (int i = 0; i < n; i++)
                up[i][j] = up[up[i][j-1]][j-1];
    }
    int kthAncestor(int u, int k) {
        for (int j = 0; k > 0; j++, k >>= 1)
            if ((k & 1) == 1) u = up[u][j];
        return u;
    }
}
#
★★

5. ACL lazysegtree 的四个参数中 op/e/mapping/composition 如何抽象区间修改与查询,普通线段树为何是特例

请说明 AtCoder Library(ACL)lazysegtree 的四个模板参数 op/e/mapping/composition,以及普通线段树为何是其特例?

  • op、e:节点合并与单位元,定义区间查询
  • mapping、composition:懒标记如何作用于节点值、懒标记如何组合
  • 普通线段树(无懒标记)是 mapping 恒等、composition 无意义的特例

ACL 的 lazysegtree 用四个抽象参数把线段树泛化:op(a,b) 定义两个子节点如何合并成父节点(如求和、取 max);e 是结合 op 的单位元(如 0、-inf);mapping(f,x) 定义懒标记 f 如何作用于节点值(如区间加 x 时把节点值+x);composition(f,g) 定义两个懒标记如何合并(先应用 g 再应用 f)。通过这四个参数,同一套线段树代码可服务于区间加+和、区间赋值+最值、区间乘+和等任意"可结合、可作用"的代数结构。普通线段树(只有区间查询、无区间修改)是 mapping 为恒等变换、composition 无操作的特例,此时只需前两个参数;其余情况 lazy 标记的"作用"与"组合"必须满足结合律与分配律以保证正确性。

ACL 的抽象本质是"把线段树代数化":op 定义半群,e 定义单位元,mapping 定义作用,composition 定义标记合成。只要满足 op 的结合律、e 单位元、mapping 分配律,就能复用一个实现。这比手写每种特定线段树更简洁、不易错。

// 区间加 + 区间和:op=sum, e=0, mapping(加x, 节点值+ x*len), composition 相加
// 区间取 min + 区间最大值:op=max, e=-inf, mapping(chmin x, 节点值=min(v,x)), composition 取 min
#

6. AtCoder Library 的常用模块中 convolution、lazysegtree、DSU、SCC 的接口与复杂度?

请说明 AtCoder Library 常用模块 convolution、lazysegtree、DSU、SCC 的接口与复杂度?

  • convolution:多项式乘法,NTT 实现
  • lazysegtree:区间修改+区间查询线段树
  • DSU:并查集,按秩+路径压缩

ACL 的常用模块:convolution(a,b) 计算多项式乘法,内部用 NTT(对 NTT 友好素数)O(n log n);lazysegtree 提供泛化区间线段树,修改与查询 O(log n);DSU(并查集)支持 union/leader/size 等,均摊 O(α(n));scc 用 Tarjan 求强连通分量,O(n+m),返回每个顶点所属分量编号。这些接口设计成 C++ 模板,兼顾性能与易用。convolution 是生成函数/多项式题的核心,lazysegtree 覆盖区间数据结构,DSU 处理连通性,SCC 处理有向图分量。

ACL 是 AtCoder 竞赛官方库,接口统一、性能可靠。每个模块面向一类经典问题:convolution 处理多项式/卷积,lazysegtree 处理区间,DSU 处理合并,SCC 处理有向图。理解其复杂度即可按需选用。

#

7. ACL 的 convolution 实现为什么限定 mod 998244353 等 NTT 友好素数,传入任意模数时内部如何处理?

请说明 ACL 的 convolution 为何限定 998244353 等 NTT 友好素数,以及传入任意模数时内部如何处理?

  • NTT 需要原根与 2 的幂次整除 p-1,998244353 = 119*2^23+1
  • 任意模数场景用"多个 NTT 友好素数 + CRT 合并"
  • 计算精度与步骤

NTT(数论变换)要求模数是 NTT 友好素数:其 p-1 需含有足够大的 2 的幂因子(长度 n 的变换要求 2^k | p-1),且存在原根。998244353 = 119*2^23 + 1,支持长度达 2^23 的变换,是常用选择。当用户传入任意模数(非 NTT 友好)时,ACL 无法直接对该模数做 NTT,内部做法是:把模数分解成若干 NTT 友好素数的乘积(如 998244353、1004535809、469762049 等),分别对每个素数做 NTT 卷积,再用中国剩余定理(CRT)合并结果,还原到目标模数下的值。若数据规模使真实值超过各素数乘积,还需拆分子段(利用多项式长度限制)保证每段乘积不溢出 CRT 阈值。

核心是"变换对模数结构有要求"。NTT 友好素数保证可做高效的幂次变换;任意模数则通过"多素数 NTT + CRT 合并"绕开。工程上这是"把难题转化成已知可解步骤"的经典案例。

#

8. EDU 二分与倍增思想中如何用"可行性单调"统一处理最优值问题?

请说明 Codeforces EDU 的二分与倍增思想如何用"可行性单调"统一处理最优值问题?

  • 具备单调性(可行性随参数单调)的最优值问题可二分
  • 倍增用于"步长/规模"的二进制跳
  • 两者都依赖"可分解"的性质

EDU 二分与倍增思想的核心是把"最优值/步长"问题通过单调性统一。二分答案适用于"可行性随阈值单调"的问题:把最优值转化为判定,二分阈值找临界点。倍增适用于"步长可二进制分解"的问题:预处理 2 的幂步长,查询时按位跳。二者的共同前提是"单调性/可分解性":二分依赖可行性单调,倍增依赖"跳 2^i 由两次 2^(i-1) 组合"。工程上,二分解决"答案是某个值",倍增解决"从某点出发能走多远/跳多高",互为补充地覆盖了最优值与路径类问题。

"可行性单调"是二分答案的充分条件:一旦判定 ok(x) 单调,就能用二分把优化问题转成 log 次判定。倍增则用二进制表示解决"在线查询的步长"。理解二者都以"可分解、可组合"为前提,就能举一反三。

#

9. ACL 在 convolution 与 FFT/NTT 的工程接口。

请说明 ACL 在 convolution 与 FFT/NTT 的工程接口设计?

  • convolution 函数接口:输入两个向量,输出卷积
  • 内部自动选择 NTT 实现与模数处理
  • 对调用方透明,屏蔽 FFT/NTT 细节

ACL 的 convolution 以简洁接口封装了 FFT/NTT:调用 convolution(a, b) 传入两个向量,返回卷积结果向量。内部自动选择实现:若模数是 NTT 友好素数则直接 NTT;否则走多素数 NTT + CRT;若数据规模小(如 n*m 很小)则可能退化为朴素算法以避免常数开销。接口对调用方完全透明,隐藏了单位根、位反转、CRT 等细节,只暴露"输入输出向量"。其复杂度 O(n log n)。这体现了"工程接口封装复杂算法"的设计:算法细节在库内,用户只关心语义。

接口设计的关键是"透明 + 自动选择最优实现"。FFT/NTT 的位反转、蝶形运算、CRT 合并等都是易错细节,ACL 把它们封装进库,用户只需理解卷积语义。这也是"工程化竞赛库"的价值。

#

10. ACL 在 maxflow 与 mincostflow 与 scc 与 twosat 的工程接口。

请说明 ACL 在 maxflow、mincostflow、scc、twosat 的工程接口设计与复杂度?

  • maxflow:最大流 Dinic,接口加边/求流
  • mincostflow:最小费用流,支持费用与流量
  • scc:强连通分量

ACL 提供四类图论/逻辑模块:maxflow(Dinic 实现)接口为 add_edge(from,to,cap)flow(s,t),返回最大流,复杂度 O(V^2 E);mincostflow 接口 add_edge(u,v,cap,cost)flow(s,t,flow_limit),返回最小费用与流量,基于 SSP(Successive Shortest Path,每次用最短路增广,常配合 Bellman-Ford/SPFA);scc 接口 scc_graphadd_edgescc(),返回每个顶点所属分量,O(V+E);twosat 接口 two_satadd_clausesatisfiable/answer,用 SCC 求解 2-SAT,O(V+E)。这些接口把经典算法封装成简洁调用,隐藏内部数据结构。

四个模块分别解决网络流、带费用流、有向图分量、布尔逻辑可满足性。工程接口统一为"建图 + 求解",把 Dinic、最短路增广、Tarjan、SCC 判定等细节埋进库内。理解复杂度与适用场景即可选择。

#

11. ACL 在 segment tree 与 lazy segment tree 的工程接口。

请说明 ACL 在 segment tree 与 lazy segment tree 的工程接口设计?

  • segtree:单点更新 + 区间查询,op/e 参数
  • lazysegtree:区间更新 + 区间查询,op/e/mapping/composition
  • 接口对调用方透明,隐藏递归与懒标记

ACL 的 segtree 提供单点更新与区间查询:构造时传入 op 与 e,set(p,x) 单点赋值,prod(l,r) 区间查询,max_right/min_left 支持二分查找,复杂度 O(log n)。lazysegtree 在其上增加区间更新:传入 op/e/mapping/composition 四个参数,apply(l,r,f) 区间应用懒标记,prod(l,r) 区间查询,复杂度 O(log n)。接口把递归、节点合并、懒标记下传全部封装,用户只需描述"代数结构"(合并规则、单位元、标记作用)。工程上这使同一套代码可处理和、最值、最大子段和等多种结构。

接口设计的关键是"把代数结构参数化":用户不写线段树递归,只定义 op/e/mapping/composition。这使得代码正确性更易保证、复用性极强。segtree 是 lazysegtree 在无区间修改时的特例。

#

12. ACL 在 ACL Contest 的实战工程实现。

请说明 ACL 在 ACL Contest 中的实战工程实现与使用要点?

  • ACL Contest 是 AtCoder 为推广 ACL 而设的专题赛
  • 各题直接对应 ACL 模块(convolution、lazysegtree、DSU、SCC 等)
  • 实战要点:接口选择、复杂度、模数处理

ACL Contest(AtCoder Library Contest)是 AtCoder 举办的专题赛,题目设计为直接使用 ACL 的各模块,帮助选手熟悉官方库。实战中,选手根据题目类型选择模块:多项式/卷积题用 convolution,区间题用 lazysegtree,连通性用 DSU,有向图分量用 scc,逻辑约束用 twosat,网络流用 maxflow/mincostflow。实战要点包括:确认复杂度与数据规模匹配(如 convolution 需 NTT 友好素数或任意模数 CRT)、注意 lazysegtree 参数的结合律/分配律、以及 DSU 的均摊复杂度。ACL 的意义是让选手从"手写数据结构"转向"熟练选用库",节省编码时间、减少 bug。

ACL Contest 的工程价值是"库的实战验证":选手需理解每个模块的适用场景与边界,而非重复造轮子。实战中判断"该用哪个模块、复杂度是否够、参数是否满足"是核心能力。

#

13. Codeforces EDU 二分查找章节在单峰与单调与旋转的工程实现。

请说明 Codeforces EDU 二分查找章节对单峰、单调与旋转数组三类问题的工程实现?

  • 单调序列:标准二分找目标
  • 单峰(凸/凹)序列:三分求极值
  • 旋转数组:分段有序,二分定位

EDU 二分章节覆盖三类问题:单调序列上用标准二分查找 target(或 lower_bound/upper_bound),依赖单调性;单峰(unimodal)序列上求极值用三分(ternary search),比较两个相邻点判断峰在左还是右,O(log n);旋转数组(有序数组被旋转)利用"分段有序"性质,先判断 mid 落点在哪段有序区间再二分。三类问题共性是"利用序列的结构性质缩小搜索区间"。工程上注意边界写法(闭/开区间、+1/-1)与重复元素处理。

单调是二分的前提;单峰用三分(因为单个比较点无法判断方向,需两个点);旋转数组是"分段单调"的二分。理解"数据有什么结构,就能设计什么搜索"是本章核心。

#

14. CSES Problem Set Geometry 章节在凸包与旋转卡壳与圆交的工程实现。

请说明 CSES Geometry 章节中凸包、旋转卡壳与圆交的工程实现?

  • 凸包:Andrew 单调链 O(n log n)
  • 旋转卡壳:对凸包求最远点对/直径 O(n)
  • 圆交:两圆相交面积/点

CSES Geometry 章节覆盖经典计算几何:凸包用 Andrew 单调链(先排序后分上下壳构建)O(n log n);旋转卡壳(rotating calipers)在凸包上通过双指针/旋转方向找最远对(直径)、最宽等,O(n),是凸多边形最优化查询的利器;圆交问题求两圆相交面积/交点,用解析几何(圆心距、半径关系分情况)。工程实现要注意浮点精度(eps 比较)、向量叉积符号、极角排序等细节。三类问题分别是"凸包构造、凸包上最优化、圆与圆关系"的基础。

计算几何工程的核心是"几何性质 + 数值精度"。凸包用单调链保证 O(n log n);旋转卡壳利用凸多边形单调性 O(n) 求直径;圆交分情况讨论圆心距。理解叉积/点积的几何意义是基础。

#

15. Codeforces EDU HLD 章节在路径查询与子树查询的工程实现。

请说明 Codeforces EDU HLD(重链剖分)章节如何实现路径查询与子树查询?

  • HLD 把树剖分成重链,用 DFS 序映射到线性数组
  • 路径查询:沿重链跳,用数据结构维护链段
  • 子树查询:DFS 序连续区间

重链剖分(HLD)把树按重儿子划分成若干重链,并用 DFS 序把树映射到线性数组,使每条重链的节点在数组上连续、每个子树也对应连续区间。路径查询(如 u 到 v 的最值/和)通过"沿重链向上跳"分解成 O(log n) 段连续区间,每段用线段树/树状数组查询,总 O(log^2 n)。子树查询则因为子树在 DFS 序上是连续区间,直接区间查询 O(log n)。工程上需先做两遍 DFS 记录 size、重儿子、链头、深度与 dfn,再建数据结构。HLD 把"树上路径问题"转化为"数组区间问题",是树剖分思想的典型。

HLD 的价值在于"把任意路径分解为 O(log n) 个连续区间",从而复用序列数据结构。子树查询因其 DFS 序连续性更简单。理解"重链的连续性 + 轻重儿子划分"是核心。

#

16. Codeforces EDU Suffix Automaton 章节在子串查询的工程实现。

请说明 Codeforces EDU 后缀自动机(SAM)章节在子串查询中的工程实现?

  • SAM 是识别所有子串的最小 DFA,O(n) 构造
  • 子串出现次数、不同子串数、最长公共子串等查询
  • 结合 endpos 集合与拓扑序

后缀自动机(SAM)是接受一个串所有子串的最小 DFA,构造 O(n)(摊还线性),是子串查询的利器。每个状态对应一组 endpos 相同的子串,状态个数 O(n)。工程实现包括:构建时维护 last 与转移表,建完后按长度拓扑排序累加 endpos 大小(出现次数),通过 DP 计算不同子串数,或对两个串建 SAM 求最长公共子串(在建第二个串的 SAM 上匹配)。子串出现次数 = 该状态 endpos 集合大小;不同子串数 = 各状态 len 差之和。工程上常用数组模拟转移表,注意内存与转移的稀疏性。

SAM 的核心是"endpos 等价类":同一状态内的子串 endpos 相同,出现次数与长度信息都集中在状态上。构造基于"增量插入 + 后缀链接"(link),配合拓扑序做 DP 即可回答多数子串统计问题。它是"以空间换时间"的经典(O(n) 状态)。

#

17. 竞赛库与工程库的差异中 ACL 与 STL 在内存/异常/可读性上的取舍?

请说明竞赛库(如 ACL)与工程库(如 STL)在内存、异常、可读性上的取舍差异?

  • 竞赛库追求速度与简洁,用静态数组、无异常、可读性让步
  • 工程库要求健壮性、异常安全、可维护性
  • 内存与可读性的权衡

竞赛库(ACL、KACTL)与工程库(STL)目标不同:竞赛库追求极致性能与编码简洁,常用静态数组、位运算、无异常处理、代码紧凑,可读性让位于速度与书写效率,且不关心异常安全与内存泄漏(单次运行程序);工程库(STL)要求健壮性、异常安全、可维护性、可复用性,用模板、RAII、迭代器抽象,代码长但可读可维护,内存管理透明。取舍本质是"一次运行 vs 长期维护":竞赛代码跑一次即弃,工程代码要长期演化。因此 ACL 用固定大小数组省去动态分配,STL 用容器与异常安全保证正确性。

差异源于目标约束:竞赛时间是硬约束,常数与书写速度优先;工程是可靠性优先,可读性与健壮性优先。理解"环境决定取舍"能帮你判断应选择哪种风格。

#

18. ACL 的数论函数中 floor_sum、inv、pow_mod、cr 的用途与复杂度,如何与扩展欧几里得衔接

请说明 ACL 数论函数 floor_sum、inv、pow_mod、cr 的用途与复杂度,以及它们与扩展欧几里得的衔接?

  • pow_mod:快速幂取模
  • inv:模逆元,扩展欧几里得或费马小定理
  • floor_sum:类欧几里得求 sum floor((a*i+b)/m)

ACL 的数论模块涵盖:pow_mod(a,b,m) 快速幂 O(log b);inv(x,m) 求模逆元,当 m 为素数时用费马小定理 pow_mod(x,m-2,m),否则用扩展欧几里得求解 ax+my=1;floor_sum(n,m,a,b) 用类欧几里得算法 O(log m) 求 sum_{i=0}^{n-1} floor((a*i+b)/m);cr 用中国剩余定理合并形如 x ≡ a_i (mod m_i) 的同余方程组,内部用扩展欧几里得求互素模数的合并。这些函数与扩展欧几里得关系密切:模逆元、CRT 合并、以及贝祖等式求解都依赖扩展欧几里得求逆/特解。

扩展欧几里得是数论地基:它能解 ax+by=gcd(a,b),从而得到模逆元、CRT 系数、以及线性同余。ACL 把这些封成函数,方便组合使用。理解"逆元本质是扩展欧几里得的特例"是衔接点。

#

19. ACL 的 string 模块中 suffix_array、lcp_array、z_algorithm 的接口与典型组合用法

请说明 ACL string 模块 suffix_array、lcp_array、z_algorithm 的接口与典型组合用法?

  • suffix_array:后缀数组 O(n log n)
  • lcp_array:LCP 数组,与后缀数组组合用于字符串比较
  • z_algorithm:Z 数组,求每个位置与开头的最长公共前缀

ACL 的 string 模块提供:suffix_array(s) 返回后缀数组(按字典序排序的后缀位置),O(n log n);lcp_array(s, sa) 返回相邻后缀的 LCP 数组(height),O(n);z_algorithm(s) 返回 Z 数组,其中 Z[i] 是 s[i..] 与 s 的最长公共前缀长度,O(n)。典型组合:后缀数组 + LCP 数组是字符串题的核心工具,可求不同子串数(sum len - LCP)、最长重复子串、出现次数等;Z 数组则用于模式匹配(把 pattern + '#' + text 求 Z,Z[i]==pattern 长度即匹配)、以及求循环节。三者把"字符串比较/匹配/统计"问题标准化。

后缀数组与 LCP 解决"所有后缀的次序与公共前缀",Z 数组解决"与开头的公共前缀"。组合用法围绕"子串比较转为 rank 比较 + LCP 查询"。ACL 把这些封装成 O(n) 或 O(n log n) 的接口,避免手写易错细节。