标记 \(*\) 的是我自己取的名字。
数学
技巧
组合意义:将和式与积式转化为具有组合意义的具体问题,通过分析这些具体问题来解决原问题。
精题:AT_arc147_d,P4351,CF1842G,CF1097G
\(*\)覆盖集:一些问题中,无论对象的某一部分 \(A\),而剩下的一部分 \(B\) 一定可以通过调整满足限制,且唯一确定,那么我们就可以认为 \(A\) 部分可以任意选择,简化统计。
精题:CF894B,AT_arc147_d
二项式定理的配凑:当遇到类似 \(\sum_{i}\sum_{j}x^{i+j}y^i\) 形式的式子时,我们可以考虑转换枚举量为 \(k=i+j\) 和 \(i\),得到 \(\sum_{k}\sum_{i}x^{k-i}y^i\),利用式子中的组合数配凑成为二项式定理。
精题:P4351
01 转化:\(\sum_ia_i=\sum_{x\in V}\sum_i[a_i\ge x]\)。因此,与大小有关的题,我们可以考虑枚举 \(x\),将序列转换为 \(0\) 和 \(1\)。这样可以压缩值域信息,便于计算。
精题:CF2140E2,AT_arc139_d
单位根反演:记 \(\omega_{n}^k\) 为单位根。可以得到如下式子,这个式子被称作单位根反演。可以通过考虑不整除的情况等比数列证明。
精题:P5591
\(*\)换系恒等式:一种组合恒等式,可以通过展开证明。
精题:P5591
题目
AT_arc147_d [ARC147D] Sets Scores
考虑得分的组合意义。我们从 \(1\) 到 \(n\) 依次从包含这个数的集合中选择一个,方案数即为得分。
对这个东西做组合意义。考虑对某一个固定的选取的集合的序列,统计有多少满足要求整数集合序列。这样的选取的集合的序列有 \(n^m\) 个。
我们发现,对称差中恰好有 \(1\) 个元素等价于序列中除第一个集合,之后每一个集合和前一个相差且仅相差一个元素。也就是说,利用第一个集合和 \(n-1\) 个元素,我们就可以唯一确定这个整数集合序列,形成双射。
考虑对第一个集合和 \(n-1\) 个元素计数。选取的集合的序列本质上是要求某个集合中存在某一个元素。注意到,如果 \(n-1\) 个元素已经确定,我们可以使用第一个集合满足所有性质,因为在第一个集合中所有元素互不影响,且此时第一个集合是唯一确定的。
因此,方案数即为 \(n-1\) 个元素任意选择,方案数为 \(m^{n-1}\)。利用乘法原理,最后的答案为 \(n^mm^{n-1}\)。
P4351 [CERC2015] Frightful Formula
考虑组合意义,即为向下走有 \(a\) 条路,向右走有 \(b\) 条路,每个点都有 \(c\) 条路直接到,第一行和第一列的点分别有 \(l_i\) 和 \(t_i\) 条路直接到,求最后的方案。
先转化贡献体到每一个 \(l_i\)。发现向下走和向右走的次数是定的,可以直接算出 \(a,b\) 的贡献。向下走一步之后就变成了向右向下随便走,组合数直接计算出方案数。\(t_i\) 的贡献同理。
然后考虑 \(c\),与 \(l_i\) 同理。简单换个元,即改为枚举距离为向右走 \(i\) 步,向下走 \(j\) 步,写成和式。
利用二项式定理的配凑技巧,令 \(k=i+j\)。
考虑分讨消掉 \(\min\) 和 \(\max\)。若 \(k\le n-2\),则有如下式子。
否则,若 \(k\gt n-2\),有如下式子。
利用组合意义,\(f_i\) 表示走 \(i\) 步的贡献,等于走 \(i-1\) 步横着走一步或竖着走一步,减去超出格子的贡献,可以递推。
AT_arc139_d [ARC139D] Priority Queue 2
使用 01 转化,有 \(\sum_ia_i=\sum_{x\in V}\sum_i[a_i\ge x]\),于是枚举 \(i\),我们就只关心最终有多少个数大于等于 \(i\)。
记有 \(c\) 个数大于等于 \(i\),然后考虑插入一个数对 \(c\) 的影响。如果插入的数小于等于 \(i\),那么 \(c\) 不变,否则 \(c\) 加 \(1\)。
考虑删除,发现 \(c\) 已经包含了与 \(i\) 相关的第 \(k\) 小信息:如果 \(c\gt n-x+1\),那么会删除到大于 \(c\) 数,导致 \(c\) 减 \(1\),否则不变。由于每次插入后都会删除,这相当于把 \(c\) 对 \(n-x+1\) 取 \(\min\)。
于是 \(c\) 的变化只与插入的数是否大于等于 \(i\) 有关,而每次是否大于等于 \(i\) 的选法是固定的,小于 \(i\) 的选法也是确定的,于是我们只需要枚举大于等于 \(i\) 的次数 \(p\),利用组合数选出位置,就可以算出来最后的贡献为 \((m-i+1)^p(i-1)^{k-p}\binom{k}{p}c\)。这个 \(c\) 可以由上一段根据 \(p\) 算出来。
需要注意取 \(\min\) 需要满足初始的 \(c\) 小于等于 \(i\)。若初始的 \(c\) 就大于 \(n-x+1\),每一次大于等于 \(i\) 没有效果,但小于等于 \(i\) 会导致减 \(1\),且最小只会减到 \(n-x+1\)。
CF1097G Vladislav and a Great Legend
高次非常不好做,考虑利用斯特林反演消掉高次项。
考虑 \(\binom{f(S)}{i}\) 的组合意义,为在每一棵生成树中选出 \(i\) 条边的方案数之和。考虑树形背包 DP 一次全部计算出来,设 \(f_{x,i}\) 表示在子树 \(x\) 中选出 \(i\) 条边的方案数,注意这里只是子树 \(x\) 内,\(x\) 不一定为虚树的根。
考虑合并两棵子树 \(x,u\)。先考虑 \(x\) 点不为虚树的根,可以从原本的子树直接继承,有 \(f_{x,i}\to f'_{x,i}\),也可以从新的子树直接继承,并考虑是否选 \((x,u)\),有 \(f_{u,i-1}\to f'_{x,i+0/1}\)。
然后考虑 \(x\) 点为虚树的根,即左右子树都选了点,枚举左右选的边数,同时枚举 \((x,u)\) 选或者不选,有 \(f_{x,i}f_{u,j}\to f'_{x,i+j+0/1}\)。需要注意最后统计答案的时候枚举的是虚树的根,因此只有这一类转移的贡献才会被记入最终答案。
由树形背包的时间复杂度,时间复杂度为 \(O(nk)\)。
P5591 小猪佩奇学数学
注明出处。
注意到 \(\left\lfloor \frac{i}{k} \right\rfloor = (\sum_{j=0}^{i}\,[k\,|\,j]) - 1\),于是单位根反演后进行如下推导。
注意到在 \(d=0\) 的时候等比序列求和分母为 \(0\),会挂,需要特判掉。
AT_agc045_d [AGC045D] Lamps and Buttons
由于根本不知道排列,所以怎么操作都无所谓,于是按 \(1\) 到 \(n\) 的顺序操作,如果让某盏灯灭了就再打开,否则下一盏。
由于是排列,于是我们考虑置换环。我们发现会输当且仅当没开的灯里有自环,或操作到了一盏有自环的灯。没开的灯里有自环对灭掉的灯限制即可,主要考虑操作到了一盏有自环的灯。
考虑枚举第一个有自环的亮灯的位置 \(t\)(没有则记作 \(A+1\)),题目转化为了有自环的亮灯前没有自环,且后面的灯都连进了 \(t\) 前面的灯的环。
考虑容斥处理没有自环,转化为了钦定前面有 \(k\) 个自环。于是先用组合数选出这些自环的位置,然后先插入 \(a\) 个 \(t\) 之前的位置,任意插入一个位置或自己成为一个自环,假设已经插入了 \(siz\) 个,方案数为 \(siz+1\)。然后插入未亮灯的位置,不能形成自环,方案数为 \(siz\)。最后插入 \(t\) 之后亮灯的位置,没有限制,方案数为 \(siz+1\)。整理,得到最后的式子为 \(\binom{t-1}{k}\frac{a(a+b+c)!}{a+b}\)。
图论网络流
技巧
点双二级结论:对于点数 \(\geq 3\) 的点双,任给两点 \(x\neq y\),存在经过 \(x, y\) 的简单环。
对于点数 \(\geq 3\) 的点双,任给一点 \(x\) 与一边 \(e\),存在经过 \(x, e\) 的简单环。
对于点数 \(\geq 3\) 的点双,任给三点 \(x\neq y,x\neq z,y\neq z\) 与一边 \(e\),存在 \(x \to y \to z\) 的简单路径。
对于点数 \(\geq 3\) 的点双,任给两点 \(x\neq y\) 与一边 \(e\),存在 \(x \to e \to y\) 的简单路径。
精题:P8456,AT_arc153_f
Dilworth 定理:在一张 DAG 中,若存在 \(u\) 可以到达 \(v\),\(v\) 可以到达 \(w\),则存在 \(u\) 的一条出边可以直接到达 \(w\)。若 \(u\) 可以到达 \(v\),我们称 \(u\) 比 \(v\) 大,\(v\) 比 \(u\) 小。
在偏序集中,链是任意两个元素都可以比较大小的点的集合,反链是任意两个元素都不可以比较大小的点的集合。最小链覆盖指使用最少的链不重不漏地覆盖偏序集中所有点。
由于是偏序集,最小链覆盖等价于最小点覆盖,再转化为点数减最大独立集,再转化为二分图匹配。将每个点 \(x\) 拆成入点 \(x_0\) 和出点 \(x_1\),源点连所有 \(x_0\),所有 \(x_1\) 连向汇点,容量均为 \(1\)。对于原图中的边 \(u\to v\),我们连 \(u_0\to v_1\),容量为 \(1\),点数减最大流即为最小链覆盖。
Dilworth 定理指出,在偏序集中,最长反链等于最小链覆盖。
精题:P4298,CF1630F
题目
CF555E Case of Computer Network
显然,一个边双中一定存在一个经过所有点的环,因此我们把这个环上的所有边同序定向,在同一个边双中的 \(s\) 与 \(d\) 就不用考虑了。这样显然是优的。
于是缩边双,得到一棵树。\(s\) 对应的点双 \(s'\) 到 \(t\) 对应的点双 \(t'\) 的路径为 \(s'\to\text{lca}(s',t')\to t'\),则 \(s'\to\text{lca}(s',t')\) 的边需定为向上,\(\text{lca}(s',t')\to t\) 的边需定位向下。使用树上差分维护,如果有一条边既需要向上又需要向下,无解。
P8456 「SWTR-8」地地铁铁
补集转化,减去不合法的,要么路径全为 \(0\),要么路径全为 \(1\),要么路径全为 \(0\) 或 \(1\)。
简单路径定义为不经过重复节点的路径,考虑缩点双建立圆方树。先考虑路径全为 \(0\) 的,使用点双二级结论第 \(4\) 条得不能经过含有 \(0\) 的点双,因此我们把含有 \(0\) 的点双的方点删掉,答案为剩下的每个大小为 \(siz\) 的连通块 \(\binom{siz}{2}\) 之和。注意 \(siz\) 不算方点。全为 \(1\) 的同理。
考虑路径全为 \(0\) 或 \(1\) 的。首先这两个点只能在同一点双,否则会经过同一个点,可以取 \(0\) 的路径的前一段和 \(1\) 的路径的后一段组成一条既有 \(0\) 又有 \(1\) 的路径。
若同一个点双中存在这一对点,则 \(0\) 路径经过的点和 \(1\) 路径经过的点之间没有连边,反之亦然。因此充要条件为点双内有且仅有两个同时与 \(0\) 边和 \(1\) 边相连的点,且一个点双内至多只有一个。可以直接计算。
P7368 [USACO05NOV] Asteroids G
建立最大流,源点往行对应的点连边,列对应的点汇点行连边,容量为 \(1\)。行对应的点往同一行的小行星连边,同一列的小行星往列对应的点连边,容量为 \(\infty\)。
P2766 最长不下降子序列问题
第一问显然,直接 DP,记答案为 \(mx\)。
第二问考虑建立最大流,DP 倒着做,\(f_i\) 为以 \(i\) 开头到结尾的 LIS 长度。把每个位置拆点成入点和出点,入点向出点连边,容量为 \(1\),因为每个数只能用一次。源点连向 \(f_i=mx\) 的位置的入点,\(f_i=1\) 的位置的出点连向汇点,对于每一个 \(i\),若 \(j\) 满足 \(j\gt i,a_i\le a_j,f_i=f_j+1\),则 \(i\) 的出点向 \(j\) 的入点连边,容量均为 \(1\),因为需要区分不同序列。最大流即为答案。
第三问还是考虑建立最大流,将第二问中 \(1\) 和 \(n\) 入点到出点的边,源点到 \(1\) 的入点的边,\(n\) 的出点到汇点的边容量改为 \(\infty\),最大流即为答案。这里提一下不会导致算漏的原因,如果 LIS 长度小于等于 \(2\),结果为 \(1\),没有问题;如果 LIS 长度大于 \(2\),至少会用到一个只能用一次的数,需要被 \(1\) 的容量限制,没有问题。
CF1630F Making It Bipartite
如果 \(a\) 整除 \(b\) 的因数,\(b\) 整除 \(c\),则 \(a\) 整除 \(c\),又因为所有数互不相同,因此原图是一个偏序集。因此我们考虑把边从小的指向大的,进行重定向,发现不存在奇环等价于不存在长度大于等于 \(3\) 的链。
因此每个点 \(x\) 要么只有出边,要么只有入边,考虑把这两种情况分别拆成 \(x_0,x_1\)。发现此时大量情况存在矛盾,于是考虑使用最大独立集来刻画:显然 \(x_0\) 于 \(x_1\) 矛盾,连边;若原图中存在 \(u\to v\),则 \(u_0\) 和 \(v_0\),\(u_1\) 和 \(v_0\),\(u_1\) 和 \(v_1\) 矛盾,分别连边。
原图是偏序集,手玩发现新图也是偏序集。于是最大独立集转化为点数减最小点覆盖,等价于最小链覆盖,就可以直接做了。
AT_agc029_f [AGC029F] Construction of a tree
神秘 Ad-hoc。随便钦定一个根,将剩余的数与其所在的包含这个数的点集对应的点连边,可以构造的必要条件为存在完美匹配。
之后考虑使用 BFS 构造方案。最初,将根丢入队列。之后每次提出此时的队头,遍历所有包含队头的点集。若此时这个点集在完美匹配中的匹配点没有被连边,那么连一条这个点到队头的边,并把它加入队列。注意到这样构造不出来当且仅当图不连通,而图不连通本身就无解,是正确的。
字符串
技巧
二进制分组:对于一些便于重构而不便于插入的结构,我们可以通过二进制分组在劣一个 \(\log\) 的代价下支持插入。
具体的,我们维护 \(\log\) 个这种结构,插入时找到第一个没有被插入的这种结构,并将之前的这种结构全部推平,一起插入这个位置的结构进行重构。由于每个元素只会被插入 \(\log\) 次,所以复杂度只劣一只 \(\log\)。如 K-D Tree 和 AC 自动机。
精题:P4148,CF710F
Border 与回文串转化:对于串 \(s\),我们将其重排为 \(s'=s_1s_ns_2s_{n-1}\dots\),发现此时串 \(s\) 的 Border 就转化为了 \(s'\) 中的回文串。
精题:P3546
题目
P4696 [CEOI 2011] Matching
先将排名的位置转化为位置的排名。
设文本串为 \(s\),模式串为 \(t\),虑类比 KMP 的过程,假设我们正在匹配 \(i\) 和 \(j(i<j)\),则需要满足 \(s_i\) 在 \(s[1,i]\) 中的排名等于 \(s_j\) 在 \(s[j-i+1,j]\) 中的排名。这样定义了相等之后,我们就可以完全模仿 KMP 的过程,使用树状数组维护排名,\(O(n\log n)\)。
我们注意到已经匹配的部分排名是完全一样的,因此我们其实只需要比较 \(s_i\) 在 \(s[1,i]\) 中的前驱后继和 \(s_j\) 在 \(s[j-i+1,j]\) 中的前驱后继的相对位置是否相等。假设相等,第一部分即 \(t_i\) 在 \(t[1,i]\) 中的前驱后继,可以维护一个值域的链表位置上从尾部删除预处理出来,然后判断 \(s_j\) 在 \(s[j-i+1,j]\) 的相对位置中是否大于前驱且小于后继,若满足则一定是对的,假设相对位置不为前驱,那存在别的地方有前驱,那此时 \(t_i\) 中的前驱就不为这个位置了,矛盾。后继同理。
CF710F String Set Queries
删除是假的,可以维护一个被删除的串的集合,查询时在这个集合中查询出现次数减去即可。
AC 自动机不能插入,考虑二进制分组。每组维护有哪些串,推平就直接删除,并将这些串和新串一起转移到第一个没有的位置。重构就是直接建 AC 自动机处理 fail 指针。由于分开建会炸空间,所以建到一起,写根节点不为 \(0\) 的 AC 自动机和垃圾回收。
P3546 [POI 2012] PRE-Prefixuffix
不妨设循环同构之前是 \(AB\),循环同构之后是 \(BA\),则原串形如 \(AB\dots BA\),转化为求原串的两重 Border。
不会做,使用 Border 与回文串转化,转化为求两个紧密相连的偶回文串,且第一个回文串从 \(1\) 开始。后面的条件可以直接判定回文串末尾的后面一个位置更新答案,紧密相连考虑对每个位置预处理出覆盖这个位置的最远的回文串。
考虑枚举回文中心 \(d\),则覆盖位置 \(i\) 是回文串会延伸到 \(2d-i\),因此我们从大到小枚举 \(d\),\(d\) 一定会覆盖到所有它能覆盖的东西,即 Manacher 求出的最长回文半径。双指针维护即可。
P6292 区间本质不同子串个数
离线扫描线,扫右端点,考虑每种本质不同子串的贡献。假设串长为 \(len\),最后一次出现的位置为 \(lst\),则贡献到 \(lst-len+1\),区间 \([l,r]\) 就在插入 \(r\) 时查询 \([l,r]\) 的和。
考虑插入 \(r\) 会发生什么。先把 SAM 建出来,由 Parent Tree 的性质我们发现新增的串是位置 \(r\) 对应的节点到根的一条链,且并集恰好为 \([1,r]\)。注意到一个节点中串的长度是连续的,而 \(lst\) 是相同的,因此一个节点的贡献可以很方便的减掉。于是我们先对 \([1,r]\) 加 \(1\),然后暴力跳 Parent Tree 上的父节点到根把贡献减掉,因为它们已经出现过了,顺便更新 \(lst\)。区间加区间查,可以使用线段树维护,时间复杂度 \(O(n^2\log n+m\log n)\)。
暴力跳到根让我们联想到了 LCT 的 access 操作,考虑 LCT 维护这个过程。考虑修改的操作是什么,即到根推平。我们发现一个很好的性质:在一条实链中,所有点的 \(lst\) 的相同,而 Parent Tree 的性质告诉我们任意链上的串长是连续的,因此撤销一条实链时贡献也是一个区间,且这个区间可以通过链顶的父节点和现在的节点得到,直接线段树维护即可。
贪心、构造、博弈
技巧
缺一分治:如果我们需要求序列中缺少 \(l\) 位置的答案,我们考虑分治。递归到 \([l,r]\) 时,统计的答案为整个序列缺少 \([l,r]\) 时的答案。向下一层递归时我们算上另一半的贡献,回溯时撤销或回滚,递归的叶子节点 \([l,l]\) 的答案即为缺少 \(l\) 位置的答案。
是线段树分治的另一种表达方式。
精题:CF1442D
合并类贪心:每次选择若干个元素合并类问题,可以考虑按照操作顺序建出一棵树,考虑在这棵树上使用决策包容性或反证法证明贪心的正确性或寻找思路启发。
这种贪心另一个常用的技巧是决策延后计算,每一步只考虑当前可以确定决策的元素和它们的贡献,剩下的东西留到下一步考虑。有时也需要适当改变贡献方式,及时计算并扣除这一部分贡献避免影响后面的答案。
精题:P1090,CF2031E,P13552
支配类博弈:博弈论中,如果一方做出某个决策后,另一方一定会做出一个决策,此时先后手不变,那我们可以把这两个决策的贡献合并到下一个决策中。
一个经典的例子是先手主动取一个较劣的决策,使得后手可以取一个较优的决策,此时先手的目的是为了选之后的另一个决策,因此可以直接合并。
精题:AT_arc164_f,P6377
题目
CF1408F Two Different
考虑 \(2^n\) 时存在一种构造可以使所有元素相同:分治,先递归完下面的所有层,然后左边和右边依次各选择一个元素合并。
考虑本题可以有两种不同的数,因此令 \(k=\log n\),对 \([1,2^k]\) 和 \([n-2^k+1,n]\) 各做一次 \(2^n\) 的构造即可。
CF1172D Nauuo and Portals
考虑归约,先把第一行和第一列的传送门填好,如果此时每行每列依旧只有一个进一个出,我们就能归约到子问题。
考虑找到 \(c_j=1\) 和 \(r_k=1\) 的 \(j\) 和 \(k\),我们可以放置传送门 \((1,j),(n,1)\) 和 \((1,n),(k,1)\),这样 \(c_j=1\) 和 \(r_k=1\) 都被满足,且那之后 \(c_n\to c_j,r_n\to r_k,c_1\to c_n,r_1\to r_n\),每行每列只有一个进一个出,可以递归。
不过有一些特殊情况。例如 \(j=n\) 或 \(k=n\) 只需要 \(1\) 对传送门,同上分析 \(c,r\) 数组的变化。再例如 \(j=1\) 或 \(k=1\),直接忽略即可。
CF1965E Connected Cubes
考虑往两边构造。我们从右往左考虑每一列,将其延长到足够长的位置,并将还没考虑的列和延长出去的最远的位置垫高一个。从右往左第二列延长短于第一列,且和第一列延长的列间隔一格,我们再把每列延长的最远位置和没考虑的列垫高一格。我们发现我们相当于把每列之间插入了一列空气。
接下来就好构造了。每次全部垫高一格,用一种颜色的方块在外围排满,再延伸进每一列空气。注意这样会被卡,但每两列只需要延伸一列,就过了。
CF1442D Sum
考虑贪心。如果存在两个只取了一部分的数组,某个数组未取的第一个元素一定大于等于另一个数组取的最后一个元素,调整之。而数组是递增的,所以这个条件一直满足,会一直调整直到某个数组为满或为空。因此,至多只有一个取了一部分的数组。
于是,我们枚举取了一部分的数组,并枚举选了哪一个前缀,于是问题就转化为了不选某个位置的 01 背包问题。考虑缺一分治,递归时直接背包合并,每个节点保存一个未合并的状态用于回溯时回滚。时间复杂度 \(O(n^2\log n)\)。
P13552 鱼类考古学
假设存在一个与操作在加操作后面,则 \((x+y)\&z\le z\le(x\&y)+z\),所以所有与操作一定在加之前。问题转化为将原集合划分为 \(k\) 个集合,集合内取与,集合外取和。
拆位,从高位到低位考虑。设这一位有值的数有 \(c\) 个,如果 \(c\lt k\),显然决策所有这一位有值的数(记作 \(1\),没值记作 \(0\))自成一个集合,否则会少一个这一位的贡献,一定不优。如果 \(c\ge k\),则决策所有 \(0\) 在同一个集合,或者根本就没有 \(0\)。注意这种情况需要直接把贡献加上并去除这一位的所有 \(1\),因为最终的序列是求和,而未决策的 \(1\) 之中存在同一个集合的数,是求与。
P6377 [PA 2010] Termites
两人取的数的和是确定的,如果求出两人取的数的差,就可以求出两人取的数。因此转化为最大化先手与后手的差。
假设存在 \(i\) 使得 \(a_{i-1}\le a_i\ge a_{i+1}\),如果先手取了 \(a_{i-1}\),那后手一定会取 \(a_i\),此时构成了支配类博弈。先手取 \(a_{i-1}\) 一定是为了取 \(a_{i+1}\),否则不优,因此下一步决策是确定的,可以直接将 \(a_{i-1},a_i,a_{i+1}\) 合并为一个元素 \(a_{i-1}+a_{i+1}-a_i\)。链表一直从左往右扫,如果 \(a_{i-1}\le a_i\ge a_{i+1}\) 删除并合并即可。
现在序列一定是若干个单谷,且中间用 \(0\) 连接,即除最左最右单谷两边都能取。除开最左边的下降段和最右边的上升段,我们发现两人直接贪心从大到小取就是对的,因为取一个元素时比它大的元素已经被取过了,此时一定暴露在外。直接排序贪心。
对于最左边的下降段和最右边的上升段,一定最后取,因为取了对先手一定是负贡献。特判一下即可。
动态规划
技巧
反转 DP:DP 的本质是一个边带权 DAG,如果题目对转移顺序存在限制,我们不妨把这个 DAG 反过来转移,这样转移式反过来了,原本的计算答案的权值变成了初始值,原本的初始值变成了计算答案的权值,就实现了对 DP 顺序的反转。
精题:CF1810G
容斥 DP:在 DP 的过程中使用容斥去除不合法的情况,而不是先 DP 完再用容斥计算答案。此时的状态一般设计为合法情况的方案数,在一个不合法方案完全考虑结束之后再容斥减去,因为考虑了一半的不合法方案可能转移到别的地方。有时需要同时对合法情况,不合法情况,所有情况中的一个或多个一起做转移。
精题:AT_agc061_c,TopCoder10107
\(*\)可行域:DP 状态数尽管为 \(n\) 维,仍需注意上界不一定取满,可能存在某两维之间的和或积等关系,此时复杂度并不达到 \(n\) 维。如果无法开下,考虑滚动数组;如果不能滚动数组,考虑使用 vector 维护。
精题:P11316,P9339
题目
CF1810G The Maximum Prefix
总方案是好算的,期望通过带选择权值转化为计数。
从前往后 DP 是难以优化的,考虑从后往前 DP。设 \(f_{i,j}\) 表示考虑了 \(i\sim n\) 的最大前缀和为 \(j\) 的方案数,转移套用最大前缀和的转移,有 \(f_{i,j}\to f_{i-1,\max(j+a[i],0)}\)。最后答案统计为 \(\sum f_{i,j}h_j\)。
但题目要求的转移顺序是从前往后,否则复杂度会劣,因此我们需要把这个 DP 的转移反过来。原 DP 是一个 DAG,权值在转移边上,我们建出这个 DAG 的反图,反向转移回去。注意此时最后统计答案的 \(h_j\) 无法处理,考虑赋值到 \(f_{0,j}\) 的初值里。已经做完了。
我们最后再来考虑一下反转之后状态的含义:\(f_{i,j}\) 表示考虑了 \(1\sim i\),钦定剩下的的组成的最大前缀和为 \(j\) 的方案数,于是转移 \(f_{i,j}\to f_{i+1,\max(j+a[i],0)}\)。发现很有道理,进一步确定了正确性。
AT_agc061_c [AGC061C] First Come First Serve
考虑为什么答案不是 \(2^n\),发现如果一个区间中没有任何其他选择的点,那这个区间无论选左端点还是右端点都一样。
我们发现这个条件极其难以刻画,考虑容斥 DP。设 \(f_i\) 表示考虑完了前 \(i\) 条线段的合法方案数,每条线段可以取左端点或右端点,显然有 \(2f_{i}\to f_{i+1}\)。
考虑一条线段选左端点还是右端点都一样的限制,我们假设对于线段 \([l,r]\) 有这个限制,将这种情况减掉。此时我们找到最大的 \(r_p\lt l\) 和最小的 \(l_p\gt r\),此时 \((p,q)\) 中选法唯一:只能选左端点或右端点。又由于容斥 DP 考虑减去贡献是一个方案完全考虑结束的时候,所以有 \(-f_p\to f_{q-1}\)。\(p,q\) 可以对每个位置双指针预处理出来。
AT_abc290_h [ABC290Ex] Bow Meow Optimization
结合基础数学和贪心,我们很容易想出最后的答案一定是这种结构:左边有 \(\lfloor\frac{n}{2}\rfloor\) 只狗和 \(\lfloor\frac{m}{2}\rfloor\) 只猫,权值不降。右边有 \(\lfloor\frac{n}{2}\rfloor\) 只狗和 \(\lfloor\frac{m}{2}\rfloor\) 只猫,权值不增。如果 \(n\) 为奇数,那么权值最大的狗放在中间。猫同理。
这时有一个 \(n^3\) 的 DP。注意到此时一个点的贡献只与这个点到中心与这个点不同的数的数量,从大到小排序后从中心往两边放设 \(f_{i,x,y}\) 表示考虑了前 \(i\) 个数,中心左边有 \(x\) 只猫和 \(y\) 只狗的最小代价,直接转移。
事实上存在一个更优秀的贪心。从大到小排序后从中心往两边放,前 \(\lfloor\frac{n}{2}\rfloor\) 只狗全部放左边,前 \(\lfloor\frac{m}{2}\rfloor\) 只猫全部放右边,剩下的放另一边。
考虑证明。假设序列只有 \(0\) 和 \(1\),可以验证是成立的。对序列做 01 转化,发现每一次都是序列只有 \(0\) 和 \(1\) 的情况,决策相同,因此可以验证是对的。
P9339 [JOIST 2023] 曲奇 / Cookies
注意到这个问题和二分图具有相似的结构,考虑使用 Hall 定理。设选择的盒子为 \(x_1,x_2\dots x_n\),魔改 Hall 定理后可以得到本题合法方案的充要条件。
因此转化为了序列填数,且对每个前缀都有一个限制。这个限制可以在值域上预处理求出来,具体的,\(t\) 时刻的答案等于 \(t-1\) 时刻的答案加上 \(t\) 时刻新增的贡献,而每时刻 \(t\) 新增的贡献即为大于等于 \(t\) 的数的数量,可以读取 \(a_i\) 的时候直接对 \([1,a_i]\) 加 \(1\)。
然后考虑 DP。Hall 定理考虑最紧的情况,于是我们按从大到小的顺序考虑每种盒子大小,这样可以使不等式左边取到最大值。设 \(f_{i,j,k}\) 表示考虑到了第 \(i\) 种盒子的大小,填了 \(j\) 个数,总和为 \(k\) 是否可行,转移考虑填或不填,\(f_{i,j,k}\to f_{i,j+1,k+b_i}\) 和
\(f_{i,j,k}\to f_{i-1,j,k}\)。
发现可以使用 bitset 优化。注意可行域,记 \(S=\sum_{i=1}^n a_{i}\),显然 \(j\le\lfloor\frac{S}{b_i}\rfloor\),而 \(b_i\) 互不相同,由调和级数,\(i,j\) 两维其实是 \(O(S\log S)\) 的。时间复杂度 \(O(\frac{S^2\log S}{w})\),可以通过。
最后构造方案只需要把 \(x_i\) 搜出来然后每次取最大的 \(a_i\) 即可。
数据结构-上
技巧
三角形差分:平面内三点 \((x_1,y_1),(x_1,y_2),(x_2,y_2)\) 组成的斜率为 \(\frac{y_1-y_2}{x_1-x_2}\) 的三角形,我们可以将这个三角形这么差分。

得到了两个顶到左边界的绿色或蓝色的三角形和一个紫色的矩形。矩形直接扫描线,顶到左边界的三角形可以转化为 \(x\le x_0,y\ge y_0,kx+b\le y\),注意到 \(x\le x_0\) 是多余的,而 \(kx+b\le y\) 可以转化为 \(b\le y-kx\)。因此每个点有属性 \((y,y-kx)\),转化为了离线二维数点问题。可以做到 \(O(n\log n)\)。
精题:无
换维扫描线:对于区间对某一种数据结构进行操作的问题,一个经典的套路是换维扫描线。建立数据结构序列为横坐标,操作时间为纵坐标的二维平面,将操作视为一条横线。接下来,我们扫序列,使用数据结构维护每条横线的插入与删除,查询就变为了查询某一个时间前缀的影响,在数据结构上查询。
精题:P7560
TB5 分治:TB5 分治,又名等价类分治。狭义上,指这样一个结构:维护一个并查集,每次合并两个集合时,先建立一个虚点,将这两个集合的根节点合并为这个节点的儿子。由于需要显式维护出这个结构,所以我们既无法路径压缩,也无法按秩合并。因此我们需要额外维护一个正常的并查集来查询一个集合的根。支持撤销。有了这样一个结构,我们就可以在一个集合上带撤销地打标记,撤销时直接把标记下传即可,因为任何一个点撤销时下传标记时一定覆盖了所有需要被下传的点。
广义上,指在分治的过程中,我们只考虑每一个等价类。我们先递归到叶子节点,每个叶子节点处有常数个等价类,然后回溯时合并。我们一般先求出合并后的等价类,再根据左右子节点的信息计算出新的每一个等价类的信息。
精题:CF1814F,P7881
题目
CF522D Closest Equals
对于某一种值,显然只会考虑最近的两个位置组成的区间,否则一定不优。因此,我们将问题转化为了 \(O(n)\) 个区间,每个区间有一个权值,每次查询被一个区间完全包含的区间的最大权值。
拍到二维平面。等价于给定一堆点,每次查询 \(x\ge l,y\le r\) 的矩形。这是一个 2-side 的矩形数点,离线扫描线扫 \(r\) 后转化为单点 chkmax,前缀查询问题,树状数组解决。时间复杂度 \(O(n\log n)\)。
P9991 [Ynoi Easy Round 2023] TEST_107
有三种情况:只删了前缀,只删了后缀,前后缀都删了。
考虑前后缀都删了的情况,发现一定是删到只剩某一种值最近的两个数中间的部分。和上一道题 CF522D 除区间权值不同外完全一致,扫描线树状数组解决。
考虑只删了前缀的情况,扫描线扫 \(r\),对于某一种值一定是删到这种值最右出现的位置。因此等价于单点修改,查询大于等于 \(l\) 的最右出现的位置最小的值的位置,set 可以解决。但是 set 常数过大过不去,改成线段树二分。
考虑只删了后缀的情况,扫描线扫 \(l\),对于某一种值一定是删到这种值最左出现的位置。因此等价于单点修改,查询小于等于 \(r\) 的最左出现的位置最大的值的位置,同上使用线段树二分。时间复杂度 \(O(n\log n)\)。
P7560 [JOISC 2021] フードコート (Day1)
区间对某一种数据结构进行操作,考虑换维扫描线。把操作拍到二维平面,每个操作相当于一条水平直线,扫序列在适当时机删除即可。
查询考虑查一段前缀的信息,难以直接维护,由于删空之后就不删了,这点比较麻烦。因此,我们考虑找到最后一次删空的地方,之后就可以忽略删空之后就不删了的限制。忽略这个限制之后,我们可以使用线段树先查出一段内删掉了多少个元素,把 \(k\) 偏移这个数值,维护区间和区间和,就可以直接线段树二分查询了。这些是容易撤销的。这两棵线段树可以写到一起。
然后考虑如何求出最后一次删空的位置,一个经典的技巧是将其转化为最大后缀和的位置,即最小前缀和的位置加 \(1\)。这样每次插入时只需要修改影响到的前缀,可以直接线段树区间加维护。也是可以撤销的。时间复杂度 \(O(n\log n)\)。
CF1814F Communication Towers
考虑对值域线段树分治,一条边的作用范围为 \([\max(l_u,r_u),\min(r_u,r_v)]\),问题转化为了在值域扫到完后每个点是否与 \(1\) 号点连通过。连通性问题考虑使用并查集,进入一个节点时合并一条边两个点,使用可撤销并查集处理回溯。
考虑每次到叶子节点时对 \(1\) 号点所在的集合打标记,但难以下传,因此考虑 TB5 分治支持下传的结构。每次合并新建一个节点,每个节点记录父亲节点,用一个可撤销并查集找根。撤销的时候检查父亲节点有没有标记,有就下传并更新自己的信息,并撤销可撤销并查集上的信息。时间复杂度 \(O(n\log^2n)\)。
数据结构-下
技巧
链表维护连续段:对于每一个极长的连续段,我们记录一个链表,从连续段开头指向连续段结尾,并从连续段结尾指向连续段开头,插入一个点时可以检查左右更新指针。不支持删除,但可以撤销。
精题:P5386
在线操作分治:如果题目强制在线,而我们需要对操作进行分治计算贡献,可以考虑使用二进制分组替代分治。每次新出现一个操作的时候就按照二进制分组的方式合并,查询时只需要在 \(\log\) 个组中分别查即可。
另一种理解是分治过程中形成了一个线段树的结构,而二进制分组等价于每次加一个还没有的最靠左的叶子,然后一直往父节点跳并更新直到没有现存的兄弟节点。即分治是自顶向下的,二进制分组是自底向上的。
精题:P7881
询问分块:我们每 \(B\) 个询问进行分块。具体的,每积累 \(B\) 次修改,我们重构一次信息,并统计这 \(B\) 个修改的信息。查询时先直接查出已经修改的操作的贡献,再暴力处理最后 \(B\) 个询问的贡献。这里 \(B\) 一般取 \(\sqrt{m}\),又被称为根号重构。
精题:P5443,P7881
题目
P9809 [SHOI2006] 作业 Homework
看到取模,考虑除法根号分治。
对于 \(Y\le B\),我们可以在插入 \(X\) 的时候暴力更新,查询时直接查询。单次操作时间复杂度 \(O(B)\)。
对于 \(Y\gt B\),我们查询时可以将原序列分为形如 \([0,Y),[Y,2Y)\dots\) 的若干段,每一段查询最小值与左端点作差取最小。对于每一段的查询,我们考虑分块,每一块维护最小值和块内元素的 set,插入时直接维护。暴力查询在同一块的情况,考虑剩下的查询。对于整块,我们直接查询最小值。对于左散块,我们相当于找大于等于 \(l\) 的最小元素,直接 set.lower_bound() 即可。对于右散块,我们发现只需要检查最小值是否小于等于 \(r\) 即可。单次操作忽略 \(\log\) 低次项时间复杂度是 \(O(\frac{n}{B})\)。
取 \(B=\sqrt{n}\),平衡复杂度为 \(O(n\sqrt{n})\)。
P5607 [Ynoi2013] 无力回天 NOI2017
比较反直觉的,本题是对元素的出现次数根号分治,而不是对集合大小根号分治。
记 \(m\) 为 \(n\),转化为求并集,先离线求出所有元素的出现次数。对于出现次数 \(\le B\) 的元素,我们考虑记录所有已经包含这个元素的集合,对所有已经包含这个元素的集合和这个集合的数对 \(+1\)。使用哈希表维护集合对,时间复杂度 \(O(B)\)。
对于出现次数 \(\gt B\) 的元素,最多只会有 \(\frac{n}{B}\) 个,我们直接使用 bitset 维护集合中的这些数直接求交,时间复杂度 \(O(\frac{n}{Bw})\)。
取 \(B=\sqrt{\frac{n}{w}}\),平衡得到时间复杂度 \(O(n\sqrt{\frac{n}{w}})\)。
P5386 [Cnoi2019] 数字游戏
考虑莫队扫值域,维护序列上哪些位置满足值域限制。一个长度为 \(l\) 的连续段的贡献为 \(\frac{(l+1)l}{2}\),题目转化为单点修改,区间查询连续段的贡献。线段树维护即可,时间复杂度 \(O(n\sqrt{n}\log n)\)。
需要修改 \(O(1)\),查询 \(O(\sqrt{n})\),考虑分块均衡,将序列分成若干段,每一段段内维护块内连续段的贡献和左右极长连续段。查询时,对于散块我们暴力处理出与整块相同的这种信息,然后从左到右逐块合并,处理左边的最右极长连续段和右边的最左极长连续段合并的贡献,并撤销左右这两个极长连续段的贡献。可以使用链表维护连续段,并且链表维护连续段很方便查询连续段长度 \(l\)。
注意链表维护连续段不支持删除,因此我们使用回滚不删除莫队。时间复杂度 \(O(n\sqrt{n})\)。
P7881 [Ynoi2006] rmpq
没什么好想法,考虑询问分块,每 \(B\) 个修改操作重构一次。注意到 \(B\) 条线至多将平面划分为 \(B^2\) 个区域,于是考虑维护划分的竖线与横线与每个区域的信息。直接暴力重构一个块是 \(O(B^3)\) 的。整块查询 \(O(\frac{n}{B})\) 个块,剩下的 \(O(B)\) 个询问暴力查询贡献。查询贡献需要找到这个点在哪一个区间,可以维护划分的竖线与横线的有序序列二分找出。当 \(B=\sqrt{n}\) 时查询复杂度均衡,但重构的总复杂度来到了 \(O(n^2)\)。
考虑优化重构。对操作分治,由于平面被划分的区域是一个等价类,于是考虑 TB5 分治。考虑分治左边和右边的等价类如何合并:首先合并每一条划分的竖线与横线,直接归并合并后去重即可;然后双指针分别扫左边和右边找出每个新等价类在原左边和右边中分别属于哪个等价类,将它们的信息合并,得到合并后的所有等价类的信息。时间复杂度 \(O(B)=O(\frac{B}{2})+B^2\),带入主定理得重构的复杂度为 \(O(B^2)\)。
而这题是交互题,自带强制在线。考虑在线操作分治,用二进制分组代替分治,每次操作新建一个等价类,一路按照二进制分组的方式合并上去,时间复杂度和操作数不变,为 \(O(n\sqrt{n}\log n)\) 和 \(O(n\sqrt{n})\),由于小常数跑不满可以通过。