2025 年 9 月 OI 做题笔记
P3747 [六省联考 2017] 相逢是问候
xyd0727 B \(|\) 扩展欧拉定理 \(|\) 线段树 \(|\) 光速幂
令 \(g(i,k)\) 表示 \(a_i\) 在底下塞了 \(k\) 个 \(c\) 的值。
根据扩展欧拉定理:
发现需要计算 \(g(i,k) \bmod m\),\(g(i,k) \bmod \varphi(m)\),\(g(i,k) \bmod \varphi(\varphi(m))\),\(\cdots\);
还需要快速判断 \(g(i,k)\) 与 \(m, \varphi(m), \varphi(\varphi(m)), \cdots\) 的大小关系。记这一串数中套了 \(j\) 个 \(\varphi\) 的为 mod[j]。
对于前者,递推计算模 \(m, \varphi(m), \varphi(\varphi(m)), \cdots\) 时的 \(g(i,k)\) 即可。
同时记套了 \(j\) 个 \(\varphi\) 的数为 mod[j]。预处理 minj[i][k] 表示满足 mod[j]<=g(i,k) 的最小的 \(j\)。然后就可以判断大小关系了。
预处理这一大堆信息以后就可以上线段树了。由于取 \(\varphi\) 至多 \(O(\log)\) 次就变成 \(1\),所以区间修改直接递归到叶子。
这个预处理常数比较大,注意到本题乘方运算底数是唯一的(\(c\)),可以考虑使用光速幂优化求 \(c^x\) 的值。
具体操作就是:根号分治,令 \(K=\sqrt{\max(x)}\)。令 \(x=Kq+r\),则 \(c^x\) 可以拆为 \(c^{Kq} \cdot c^r\)。预处理 \(c^0 \sim c^{K-1}\) 以及 \(c^K, c^{2K}, \cdots\) 然后就可以 \(O(1)\) 求固定底数的幂。
然后遇到了这个帖子里的问题,照着就改过了,但暂时还没搞懂,先挂着 /kel
CF438D The Child and Sequence
CF *2300 \(|\) 线段树 \(|\) 模运算的性质
注意到关于取模的性质:当 \(a \ge m\) 时有 \(a \bmod m < \frac{a}{2}\)。
证明:令 \(a=mq+r\)。由 \(a \ge m\) 得 \(q \ge 1\)。
若 \(q \ge 2\),\(a \ge 2m+r \ge 2m\),所以 \(r < m \le \frac{a}{2}\)。
若 \(q=1\),则 \(a=m+r<2m\),所以 \(m>\frac{a}{2}\)。又 \(r=a-m\) 代入得 \(r<\frac{a}{2}\)。
直接线段树维护序列,维护区间 max 与 sum。区间对一个数 \(m\) 取模,如果区间 max 小于 \(m\) 则不必操作;否则直接修改并递归子区间。
上面的性质可以保证每个点修改不超过 \(\log\) 次。注意到赋值操作是单点修改,不影响复杂度。
CF817F MEX Queries
CF *2300 \(|\) ODT
注意到本题操作有:
-
区间推平为 \(1\)
-
区间推平为 \(0\)
-
区间 bit-flip
考虑使用 ODT 维护。查询也就是找第一个为 \(0\) 的位置,从头遍历 set 暴力找就行了。
好久没写 ODT 了,复习一下。关键是实现 split(x) 即以 \(x\) 为端点分割所在区间;遇到操作就暴力遍历或者直接推平就行了。
CF888G Xor-MST
CF *2300 \(|\) Boruvka 算法 \(|\) 01-trie
最小生成树的 Boruvka 算法:
开始时,将每个点单独置于一个联通块内;
重复进行以下步骤直到所有点都在同一个联通块内:
-
对于每个联通块 \(S\),找出一条边 \((u,v)\) 满足 \(u \in S, v \notin S\) 且 \(w(u,v)\) 最小;(通常来说这一步需要遍历所有边)
-
在最小生成树上添加这些边,并合并这些边所在的联通块。
由于每次合并联通块数量至少减少一半,所以算法整体是 \(O(m \log n)\) 的。
回到本题,此题的性质是:完全图,边权和点权有关,要求异或值最小。
常见的 Kruskal 与 Prim 算法均难以处理这种情况。这就启发我们利用本题的性质,优化 Boruvka 步骤 \(1\) 得到一个可行的算法。
看见异或值最小想到 01-trie。对于每个联通块建立一棵字典树,同时额外建立一棵全局的字典树。
对于每一轮我们枚举 \(i \in S\),剩下的问题就是如何高效找到 \(j \in V-S\) 且 \(w(i,j) = a_i \oplus a_j\) 最小的 \(j\)。
这个只要把 \(a_i\) 放到全局与 \(S\) 作差的字典树上跑就行了。合并联通块就是合并二者的 trie 树,跟线段树合并一个写法。
复杂度 \(O(n \log n \log V)\)。合并复杂度合计是 \(O(n \log n) \times O(\log V)\),找最小边是 \(O(\log n) \times O(n) \times O(\log V)\)。代码。
当时不太明白怎么算的,还挂到 U 群问了一下(

P1393 Mivik 的标题
KMP \(|\) Border 理论 \(|\) 动态规划
Border 理论初学第一题。
题意:求长度为 \(n\)、值域为 \([1,m]\) 的随机序列中,出现长度为 \(|S|\) 的某一特定子串的概率。
DP。令 \(f_{i}\) 表示以 \(i\) 结尾,\(S\) 恰好出现在 \(i-len+1 \sim i\) 且仅出现一次的方案数。
转移时用总方案数 \(m^{i-len}\) 减去不合法方案数(在 \(i\) 之前有出现 \(S\) 的情况)。
不合法情况按照 \(S\) 的第一次出现末尾位置 \(j\) 分为两类:
-
\(j \le i-len\)。贡献为 \(f_j \times m^{i-len-j}\);
-
\(j \in [i-len+1,i-1]\),也就是说第一次出现和最后一次出现有重叠。
既然有重叠那么充要条件为重叠部分是 \(S\) 的一个 border。
那就重新枚举 \(j\) 为 \(S\) 的所有 border ,转移 \(f_{i-len+j}\)。
暴力转移时间复杂度 \(O(n^2)\)。
注意到前者本质是维护 \(m^{i-len} \times \displaystyle \sum_{j=1}^{i-len} f_j m^{-j}\)。直接上前缀和就行了。
后者需要枚举 border,有点烦。这时需要一点 Border Theory 的基础:\(S\) 的所有 border 长度可以被划分为 \(O(\log |S|)\) 个等差数列。
把这些等差数列预处理出来以后,以每个数列的公差为模数,分别维护前缀和。然后就可以做到 \(n \log\) 的复杂度了。
CF558E A Simple Task
CF *2300 \(|\) 线段树
不是很常规的数据结构题。
这题主要切入点是,字符集大小为 \(26\)。
常规的交换排序单次复杂度是和区间长度有关的,显然不可行。考虑复杂度关于值域的排序——计数排序。
sort 一个区间意味着,将区间内所有字符取出来,并分别统计每个字母个数,然后从按 a-z 的顺序从左到右重新插入对应数量的字母。
于是开 \(26\) 棵线段树,每棵维护一个 0/1 串表示对应字母在该位置是否出现。需要支持区间推平,区间求和。然后直接模拟上面的过程就做完了。
CF1706E Qpwoeirut and Vertices
CF *2300 \(|\) Kruskal 重构树 \(|\) ST 表
第一反应是把这个图的最小生成树建出来(边权为边的编号),然后 \((a,b)\) 对于前 \(k\) 条边的连通性就转化为了路径上边权的最小值。
原问题就是求满足 \(p,q \in [L,R]\) 的点对 \((p,q)\) 路径上边权的最小值的最大值。发现 \([L,R]\) 不是子树限制所以做不了。
看了一眼 tag 发现是 Kruskal 重构树,然后立刻就会做了。果然对于这类题目反应还是太慢吗 /dk
更一般地,把 Kruskal 重构树跑出来,询问即为 \(\displaystyle \max_{p,q \in [L,R]} w_{LCA(p,q)}\)。
发现这个等价于 \(w_{LCA(L,L+1,\cdots,R)}\)。区间 LCA 直接上 ST 表就行了。复杂度 \(O(n \log^2 n + q \log n)\)。
还有一个更优秀的做法是,上面那个等价于 \(\displaystyle \max_{i=L}^{R-1} w_{LCA(i,i+1)}\)。同样是 ST 表维护复杂度少一只 log。
这个也是平凡的,但是我比较喜欢写多一只 log 的傻做法(
CF985F Isomorphic Strings
CF *2300 \(|\) 字符串哈希
首先注意到,两字符串同构等价于:同种字符之间的相对位置相同。
考虑维护每个字符相同字母前驱的位置,记为 \(pre(i)\);于是可以更精确地描述为:两个字符串对应位置上的 \(i-pre(i)\) 对应相同。
然后字符串哈希判断二者 \(i-pre(i)\) 的序列是否相同就做完了。
需要注意的是,子串的 \(pre\) 和原串的 \(pre\) 在某些位置并不相同,这是由于,若 \(i \in [L,R]\) 且 \(pre(i)<L\),那么子串内有 \(pre^{\prime}(i)=0\)。
只需要把这些位置的贡献在哈希值里减掉就行了。显然这些位置的个数不会超过 \(|\Sigma|\) 个。预处理这些位置复杂度 \(O((n+q)|\Sigma|)\)。
由于我写的是二分查找这些位置,复杂度 \(O(n+q|\Sigma|\log n)\)。
看了题解。前驱这么典的套路还是注意不到。想了想以前也经常因为想不到这类套路导致做不出二维数点。警钟敲烂。
CF311E Biologist
CF *2300 \(|\) 网络流 \(|\) 最大权闭合子图
算是一个最大权闭合子图的板子题。
题意就是你要把 \(n\) 个元素划分进两个集合,划分进不同的集合有不同的代价;
还有 \(m\) 个要求为指定某些元素必须同时在某个集合里,满足不了要求有对应的代价。
大概就是这个模型?直接按照这个模型跑最小割就行了。
CF1151F Sonya and Informatics
CF *2300 \(|\) 概率 DP \(|\) 矩阵乘法
首先发现最终合法形态只有 \(0\dots01\dots1\) 这一种,且同一个数字之间是不区分的。
也就是说,其实我们只关心 \(0\) 都是否在前一半。
记 \(0\) 的个数为 \(m\),定义状态 \(f_{i,j}\) 表示第 \(i\) 次交换,前 \(m\) 个数里有 \(j\) 个 \(0\) 的概率。
这时前一半有 \(j\) 个 \(0\)、\(m-j\) 个 \(1\),后一半有 \(m-j\) 个 \(0\)、\(n-2m+j\) 个 \(1\)。
转移为:\(f_{i+1,j+1} \gets \dfrac{(m-j)^2}{\binom{n}{2}} f_{i,j}\)
\(f_{i+1,j-1} \gets \dfrac{j\cdot(n-2m+j)}{\binom{n}{2}} f_{i,j}\)
令上面两个系数为 \(p_1,p_2\),那么有
\(f_{i+1,j} \gets (1-p_1-p_2) f_{i,j}\)
发现转移系数与 \(i\) 无关,考虑矩阵快速幂优化。复杂度 \(O(n^3 \log k)\)。
CF893F Subtree Minimum Query
CF *2300 \(|\) 树上问题 \(|\) 线段树合并
限制有两个:
-
在 \(x\) 的子树内
-
距离 \(x\) 不超过 \(k\),即深度 \(\in [\text{dep}(x),\text{dep}(x)+k]\)
第一个限制用线段树合并处理。第二个限制就是按深度把点权放到线段树上维护区间 \(\min\)。复杂度 \(O((n+q)\log n)\)(?)
写的过程中有一个需要注意的地方,合并一定要开新点。
理由是,若父亲的线段树继承了某个儿子的结点,再合并另一个儿子时,如果不开新点,会导致改到了先前儿子的结点上。
另解:主席树。
CF609E Minimum spanning tree for each edge
NFLS2353 E \(|\) CF *2100 \(|\) Kruskal 重构树
先跑出原图的最小生成树,包含第 \(i\) 条边 \((u,v)\) 的最小的生成树,即先去掉 MST 上 \((u,v)\) 之间的最大边,然后加上第 \(i\) 条边就解决了。
找 MST 上最大边直接建 Kruskal 重构树 + LCA 就完事了。
P3574 [POI 2014] FAR-FarmCraft
NFLS2353 F \(|\) 树形 DP \(|\) 贪心邻项交换
令 \(dp_u\) 表示 \(u\) 的子树内的人都装好所需要的最大时间。
考虑一个儿子 \(v\),首先要遍历 \(v\) 前面的所有兄弟,由于一条边来回走两遍,花费为 \(2 \sum_{x} \text{siz}(x)\);
然后进入子树 \(v\) 需要经过 \((u,v)\),花费是 \(1\);子树 \(v\) 内部需要时间为 \(dp_v\)。
得到转移方程 \(dp_u \gets \max\{dp_u,2 \sum_{x} \text{siz}(x)+1+dp_v\}\)。显然和访问各儿子的顺序有关。
为方便,以下令 \(\text{siz}\) 为原来的两倍。
考虑贪心。令 \(u\) 只有两个儿子 \(x,y\),邻项交换得:先 \(x\) 后 \(y\),\(\max\{dp_x+1,\text{siz}(x)+dp_y+1\}\);先 \(y\) 后 \(x\),\(\max\{dp_y+1,\text{siz}(y)+dp_x+1\}\)。
\(x\) 在前当且仅当 \(\max\{dp_x,\text{siz}(x)+dp_y\} < \max\{dp_y,\text{siz}(y)+dp_x\}\)。
注意到 \(dp_x < \text{siz}(y)+dp_x\) 恒成立,\(\text{siz}(x)+dp_y < dp_y\) 恒不成立。那么原式就和这两项没有关系(?),化简得:\(dp_x-\text{siz}(x) > dp_y-\text{siz}(y)\)。
其实也很好理解,\(dp - \text{siz}\) 就是跑完一趟还要等待装完的时间,这个时间越大显然安排在越前面。
然后就按照这个给儿子排序然后直接转移就完事了。
旅行
NFLS2353 G \(|\) 想不出合适的 tag
记 \(d_i\) 为从 \(i\) 开始先 \(+\) 后 \(-\) 到根的路径长度。然后你发现 \((u,v)\) 之间的路径长度就等于 \(d_u+d_v\),LCA 往上可以直接抵消。
现在问题就变成,奇数位置深度的 \(d\) 记为 \(a_1 \sim a_n\),偶数位置的 \(d\) 记为 \(b_1 \sim b_m\),求 \(a_i+b_j\) 的第 \(k\) 小值。
不难联想到某个题叫做 NOI2010 超级钢琴,拿个优先队列维护每个 \(i\) 目前取到的 \(j\) 就行了。复杂度 \(O(n+k \log n)\)。

浙公网安备 33010602011771号