OI TRICKS
位运算
每一位是独立的,可以拆开处理 (CF1879D Sum of XOR Functions)
\(a, b \in \{0, 1\}\),则
| xor | and | or |
|---|---|---|
| \(a \oplus 1 = 1- a\) | \(a \land 0 = 0\) | \(a \lor 1 = 1\) |
| \(a \oplus 0 = a\) | \(a \land 1 = a\) | \(a \lor 0 = a\) |
| $a \oplus b = a + b - 2ab $ | \(a \land b = ab\) | \(a \lor b = a + b - ab\) |
(表格最后一行其实没啥用,实际只需特判b,用上面两行,即可)
\(\forall a, b \in \mathbb{N},\ a \oplus b = a \lor b - a \land b\)
\(a \oplus b \le a + b\)
\(\max\{x \oplus a_i\} \rightarrow 01-Trie\)
二进制数常用操作
- __builtin 系列函数
- 求最高位的 1:\(2^{\lfloor \lg x \rfloor}\)
二进制集合操作
| 操作 | 集合表示 | 位运算语句 |
|---|---|---|
| 交集 | \(a \cap b\) | a & b |
| 并集 | \(a \cup b\) | a | b |
| 补集 | \(\bar{a}\) | a ^ ((1 << s) - 1) (\(s\) 是全集大小) |
| 差集 | \(a \setminus b\) | a & (~b) |
| 对称差 | \(a\triangle b\) | a ^ b |
摘自 OI-wiki
枚举子集
int s = ...;
for (int t = s; t; t = (t - 1) & s)// 遍历所有 s 的子集,注意循环中是 t = (t-1) & s 而不是 t = (t-1) & t
...
for (int t = s; ; t = (t - 1) & s) { // 这样写会把空集也算上
...
if (!t) break;
}
时间复杂度 \(O(2^{popcount(s)})\)
for (int s = 0; s < (1 << k); s++)
for (int t = s; t; t = (t - 1) & s)
...
\(O(3^k)\)
遍历所有非 0 位
for (int t = s; t; t -= t & -t) {
int b = t & -t;
int i = lg[b];
...
}
最大子矩形
用于求一个大矩形中满足某种条件的最大矩形(。。。)--> 单调栈
例题:玉蟾宫(典题),Largest Submatrix
区间数值性质
考虑区间和,前缀和
例:Parity Game
\([l, r]\)有奇数个一 \(\longrightarrow\) \([l, r]\)区间和是奇数 \(\longrightarrow\) 前缀和之差为奇数 \(\longrightarrow\) \(sum[l-1]\)和\(sum[r]\)奇偶性相同
区间加转化为单点加 ==> 差分
卡常
- 将重复使用多次的式子用一个变量存起来
a = lower_bound(x, x + n, 1ll * c * c) - x;
b = 1ll * c * c % p;
d = b + a + 1ll * c * c;
...
将 1ll * c * c存起来
long long k = 1ll * c * c;
a = lower_bound(x, x + n, k) - x;
b = k % p;
d = b + a + k;
...
- 在答案正确的前提下,尽可能避免取模,如使用__int128
a += b * c % M;
a += b * c;
if (a >= M * M) a -= M * M;
-
vector shrink_to_fit 释放多余空间
-
inline 内联函数
编译器可能会根据情况将函数展开,节省函数调用的时间,通常适用于较简单的函数,如无递归、逻辑较简单的函数,例如并查集的get(CSP2025 T2 road)
inline int get(int x)
{
return fa[x] == x ? x : fa[x] = get(fa[x]);
}
- 删去冗余变量
如果一个判断条件能够用一个变量实现,就不要用两个变量,例如 \(a \le b\),\(a\) 是递增变量,\(b\) 的值不变,则可以让 \(b\) 递减,判断 \(b > 0\) (CSP2025 T2 road)
CSP2025 T2 road 提交记录
没加 inline
加了 inline
加了 inline 并删去了一个冗余变量
- 求 \(\frac{x}{\prod a_i} \bmod p\),可以先求 \(\prod a_i \bmod p\) 再求逆元 \(O(n + \lg p)\),如果对于每个 \(a_i\) 都求逆元再乘上去是 \(O(n\lg p)\) 的
去绝对值
- 分讨
- 平方 \(|x + y|^2 = (x + y)^2\)
- 几何意义
限制绝对值中变量的值域,并分讨,去掉绝对值
例:给定\(n\)个点\(\{ (x_i, y_i) \}\),和一个定点\((x, y)\),求\(\mathrm{min} \{|x_i - x | + |y_i - y|\}\)
仅考虑\((x, y)\)左上方的点,则\(x_i \geq x\),\(y_i \geq y\),就可以去掉绝对号了,其余三个方向同理
在不等式中,根据几何意义展开即可
树上路径
树上任意两点间有唯一路径
两点的LCA是路径上很关键的一点,可以考虑以LCA为分界点将路径划为两部分讨论。
一般的,路径长与深度差可以转化,从而分离变量
若\(\mathrm{LCA}(x, y) = y\),则
\(
d(x, y) = dep_x - dep_y
\)
若\(\mathrm{LCA}(x, y) = z\),且\(z \neq x ,\ z\neq y\),则
\(
d(x, y) = (dep_x - dep_z) + (dep_y - dep_z) = dep_x + dep_y - 2 \times dep_z
\)
树的DFS正好将每条边遍历2遍
使树上若干点联通的最小边集是唯一的(树上路径并)
且等于其中任意两点路径的并集
异象石
Tarjan 求得的 SCC 编号是缩点后DAG的逆拓扑序
64位整数乘法
ull c = (long double)a * b / p;
ull x = a * b, y = c * p;
ll ans = (ll) (x - y) % p; // !!!
// 错误写法 反例:x = y = 2^32
ll ans = (ll) (x % p) - (ll) (y % p);
更方便的写法是使用 __int128
序列接龙问题
可以转化为图上问题,具体地,构造一个有向图,可以定义节点为某一前缀或后缀,边权为某一具体序列,满足起点是它的前缀,终点是它的后缀
例:\(00 \stackrel{001}{\longrightarrow} 01\)
多次排序,但可以分成两部分,其中元素相对大小不变
可以分成两部分排序,再归并起来
在一堆数 \({k_i}\) 中,不同的数至多有\(O(\sqrt{\sum k})\)
CF2148F
基数排序
每个数据有若干个关键字,求只考虑第\(k\)个及之后关键字最小的元素(CF2148F)
一般来说,如果每个关键字的值域都不大,就可以使用 计数排序 作为内层排序,此时的复杂度为 \(O(kn+\sum\limits_{i=1}^k w_i)\),其中 \(w_i\) 为第 \(i\) 关键字的值域大小。
如果关键字值域很大,也可以使用基于比较的 \(O(nk\log n)\) 排序作为内层排序,(将当前位设为第一关键字,下一位设为第二关键字),但通常是为解决特殊问题(如上例)。
-- from OIwiki
字典序问题
通常可以用贪心解决,因为将一个位置靠前的字符变小肯定比 放弃该字符,将后面的字符变小 更优,因此,处理时也可以考虑从第一位开始逐位向后考虑每个字符是否能变得更小,如果可以,就立刻修改。
例题:CF2111E
最优化
最优子结构(dp),贪心选择性质(贪心),答案单调性(二分答案),枚举
注意事项:
-
任何时候,当你得到一个求解策略,都应该将其完整、明确、清晰地将其表述出来,并检验其正确性,如果策略的答案由若干部分组成,论证每一部分都不能通过调整得到更优解
-
当你没有明确的贪心思路或你的贪心思路较为复杂且不易验证,或是不断在反例中修正,通常贪心策略是假的,应转而考虑dp
-
注意考虑各种case:如第一关键字相等,边界情况等等
-
在修正中前进
-
贪心通常不能处理这样的问题:两个元素除下标相同,但对未来答案的贡献不一样,且这个贡献难以刻画
基本问题:
-
给定一个数组\(a\),求其中选出\(k\)个元素的最大值 ==> 前\(k\)大元素和
-
最大子序列和 ==> 所有正数和
-
最大子段和 ==> 状态为以每个数为结尾的最大子段和
若题目中两个量成反比
即\(y = \frac{n}{x}\),则可以考虑根号分治或调和级数(CF1997E)
观察式子可以从奇偶性入手
括号序列问题
任意一个前缀中\(1\)的个数大于等于\(0\)的个数,整个串中\(1\)的个数等于\(0\)的个数
设\(f_i\)为前缀\([1, i]\)中\(1\)的个数与\(0\)的个数之差,则
解题时根据这两个条件列方程即可
找值出现的位置/查找某个值是否存在
如果值域不大(或可以离散化),可以用 \(vector\) 把每个值对应的位置存其来,注意:这里的值并不一定是 \(int\),也可能是 \(pair\) 等等(这种通常将数据通过 \(map\) 映射到 \(vector\),不用再离散化)
特别的,如果不要求区间查询,即每次查询针对全体数据,也可以使用哈希表及 \(map\) 存储
见哈希表
目标是让两个值相等 ==> 考虑两者的差值
操作类问题,dp状态设计
指数类元素凑数,通常满足贪心性质
即能用大的就用大的凑
二进制拆分
任意一个数都可以表示成若干个不同的二的幂的和
对于每个 \(2^i,\ i \in [0, n]\),可以选择将其加上或减去,但不能不选,可以得到 \([-2^{n+1} + 1, 2^{n+1} - 1]\) 内的所有奇数
断环成链
基本思路:删去环上一条边 变成链,枚举断哪条边
优化方法:
- 将环复制一遍接到末尾
- 通过恰当的条件与赋值,保证计算出的状态等价于把断开的位置强制相连
01矩阵
图论建模,左部点为行,右部点为列,边 \((i, j)\) 意味着点 \((i, j)\) 上填 \(1\),建出一个二分图
或者也可以加入代表格点的节点,并分别向它所在的列点与行点两边,一个格点可以拆成两个点,分别表示填 \(0\) 或 \(1\) 的状态,然后可以在上面跑网络流什么的
P10681 奇偶矩阵
CF1913E Matrix Problem
解题相关
明确目标,知道自己在干什么
寻找不变量,刻画变量
找到不变量 \(\rightarrow\) 找到并刻画(用式子表示)变量 \(\rightarrow\) 在变量与不变量之间建立联系
如果是进行操作改变数列等等:\(\rightarrow\) 考虑操作对变量的影响
不会做的时候
-
明确当前的问题/瓶颈所在
-
当你发现这题实现需要大量模拟,或没有思路时,尝试猜点结论或性质
CF2069B Set of Strangers -
检查自己的算法,找到瓶颈的地方,如果已经规约到一个非常基本的问题(如背包,最短路),很可能已经没有优化前途,尝试猜结论,或通过直接用(通项)公式计算代替算法过程
CF1954D Colored Balls -
检查自己猜的结论,它们很可能是错的(所以错误的结论把你引向了不可做的方向),尝试举出反例,修正结论或直接放弃该结论,从新方向思考问题,完整分析问题
-
检查是否读错题或漏看条件
猜结论
仔细观察样例与题目
可以猜想:答案永远不超过\(k\)?可以进行贪心?操作顺序可以交换?......
对题目的第一印象:读完题后,如果有一些想法:“如果这样安排解/数据,一定能得到最优解”等等,可以先写在纸上,后续看是否能用上,一定不要忘掉!
举出反例!!!
一定要通过反例验证自己结论的正确性
如果结论有误,并考虑自己结论的疏漏之处,过于关注个体,还是太过笼统?进行修改,例如对于前者,可以考虑若干个(甚至所有)元素的和或并,对于后者,可以考虑将整体分成若干个部分,作为新的整体,进行同样的处理(即“当整体满足......时,有......”\(\rightarrow\) “但每个部分都满足......时,有......”)
乱猜结论
有时候乱猜结论反而会起误导作用,这时还是换个角度,老老实实分析所有情况吧。
So far, we've just done guesswork on what the optimal index for the first lightning strike would be. However, during contests, sometimes this approach would land, while other times it would lead you down a rabbit hole of complex implementation, only to receive a WA at pretest 2.
What's equally important is to let go of a certain approach and start thinking in a completely new direction.
...
So what exactly did we miss? Notice that we are doing the same mistakes as we did in the first part while figuring out the optimal index for first strike, i.e, we are applying too much logic and guesswork instead of just considering all possibilities.from CF Step
过度依赖结论/贪心策略
见上面的引用
善用例子
对于简单题,仔细观察样例有可能发现一些结论
自己举出的例子或反例有可能会有指导思路修正,暗示正确答案的作用
也可以从一些具有特殊性质的特例出发,然后逐步调整,得到更一般的数据,进而得到思路。
从多个角度入手
如果某题是字符串相关的题,可以考虑KMP,Trie,Hash等字符串算法,但如果它也是一道最优化题目,也应考虑枚举,dp等思路,一定不要只关注到题目的某一个特点,而将这个题目归到某个“专题”下面,题目是综合的,是复杂的,是多元的,要从不同角度考察问题,不要将解题过程“专题化”
从值域角度入手(使用桶)
或是问题主要研究数据在数值上的性质,如数论题
尽量考虑简单的思路
不要动不动就考虑高级的数据结构,如果发现需要用,先考虑有没有更简单的替代方案,因为数据结构难写难调,容易挂
极端原理
从考虑最大/小值,最后/前面的元素等最值。
例如,题目中说选两个元素,当\(a > b\)时,将发生一些事情,那么如果我们选其中的最大/小值会怎么样?则另一元素可以取任何数,这也许是题目的突破口
或者,题目中给出若干条信息,并按顺序插入,其中包含在插入时的序列信息(如相对位置关系),那就可以从最后插入的元素入手,因为那时的序列是完整的
正难则反
CF2145D Inversion Value of a Permutation
计数与构造的思路
考虑操作的“现实”意义
如果是对一个几何对象进行操作,不妨考虑它的几何意义,让操作形象起来。
变换表述与形式以及拆解对象
如果遇到不会处理的约束条件或性质,尝试多读几遍,从另一个角度考虑其意义,将其变换一种表述形式,如将其中的约束对象分解等等
如果一个结论是关于多个对象的,尝试将它转化/分解成只关于一个(子)对象的(CF1849C)
观察问题的结构
考虑问题是否能够划分成若干个子问题,然后考虑转移,递推处理
优化
预处理
如果我们发现在某个过程(循环、函数等)中,只要其参数固定,那么整个过程及最终结果就都固定了,并且这个过程是可以递推的,所以可以预处理提升效率
均摊思想
可能每次操作的元素较多,但每个元素总共只会被操作\(O(1)\)次,于是就可以从这个角度优化,或分析复杂度
只更新可能被更新的元素/懒标记
类比SPFA优化Bellman-Ford,以及线段树
构造
常见思路:
- 通过二进制拆分拼出所有数字
调整法
-
先满足题目的部分条件,或是构造出一个次优解,通过调整使其满足其他条件或变得更优
P4180 [BJWC2010] 严格次小生成树
CF1923C Find B -
也可能是先观察具有特殊性质的例子,然后通过调整一般化
CF1922E -
也可以是先对一个元素求出以这个元素为“中心”的答案,再把“中心元素”换成其他元素,进行调整,如换根dp
CF1976C
从数据范围获得启发
关注比较小的量,可能是数据量,值域等等,从这方面入手或做好利用这些数据范围的心理准备
如果数据都很小,考虑暴力做法,或是搜索,状压dp等指数级的做法,或者也可以联想到位运算
分类讨论
不断排除自己已经讨论过的 case,剩下的就是没讨论的
不断寻找一些简单情况排除
交互题
可以考虑交互库是如何实现询问的
经典trick:二进制分组(询问每一位,问出结果)
数据结构
线段树二分
通过线段树上维护的信息判断是否有解,再递归地再左右儿子中寻找答案,常用于寻找区间内满足条件的第一个点或子区间且每次询问的条件中带有参数或是有区间操作
维护k的前驱、后继 <==> 大于/小于k的最小值/最大值
平衡树,权值线段树(不建议cdq分治(2042D - Recommendations))
如果是给定每个数(插入时刻)的后继,维护前驱,也可以使用链表,反之亦然
树状数组上倍增
比二分少一个\(\log\),查找\(sum[1, p] \geq k\)的第一个位置
int ans = 0, p = 0;
for (int i = 18; i >= 0; i--) {
if (p + (1 << i) <= n && ans + c[p + (1 << i)] < k) {
ans += c[p + (1 << i)]; // 这一行一定在下一行之前!
p += 1 << i;
}
}
return p + 1;
哈希表/map/桶 可以快速查找某个值是否存在
单调栈
可以维护一个数之前/后第一个比它小/大的数
删除序列中的一些数,使得序列单调,即执行这样的操作 若 \(\exists a_i > a_{i+1}\),删除 \(a_i\)
这个问题不能通过扫描一遍解决,因为删去一个元素后在原位置可能又出现一个满足要求的结构。也可用链表解决,但较为麻烦
并查集
-
维护位置的占用情况
可以用并查集找到第一个没被占用的空间 -
非递归写法
inline int get(int x)
{
while (fa[x] != x) x = fa[x] = fa[fa[x]];
return x;
}
区间check max
给定若干个操作:\((l_i, r_i, w_i)\) 将 \(\forall i \in [l_i, r_i],\ a_i = max(a_i, w_i)\)
-
SegmentTree Beats
-
multiset + vector: 在每个区间的左右端点处记录会在集合中加入哪些数,删去那些数,并用multiset维护操作集合
-
并查集+排序:将操作按 \(w_i\) 排序(值域不大时可使用桶)然后依次考虑每个区间,将区间中还没有被赋值的元素赋值为 \(w_i\),即用并查集维护每个元素上一个未被染色的位置
字符串
字符串匹配
利用好失配信息,借鉴KMP的思想,通过失配排除无用情况,实现扫描
字符串哈希
可以将\(O(n)\)的字符串操作(例如拼接,反转等等)转化为\(O(1)\)的
KMP
应用:求最小循环节
Trie
最基本的应用就是作为一个字典,相当于map<string, bool>
由于它是一颗树,所以经常处理与前缀有关的东西,比如求一个串与其它串的最长公共前缀(\(LCP\))的长度
还有 \(01-Trie\) 用于解决最大异或和的问题
上述两个问题的共同特点是:有大量数据,如果一个一个作会爆炸,但 \(Trie\) 可以将字符串汇集起来,批量处理,提升效率
DP
费用提前计算
递推式展开
等价于选择\(k\)个决策点\(p = \{p_1, p_2, \cdots, p_k \}\),其中\(p_k = i\),则\(f(i, k) = \sum\limits_{i = 1}^k g(p_i)\)
这样就可以算出\(G = \{g(1), g(2), \cdots g(i - 1)\}\),贪心地选取前\(k - 1\)大,就能得到\(f(i, k)\)
dp转移为枚举因数时,可以类比筛法顺转
\(O(n \lg n)\)
状态设计
尽可能贴近题目。
如果原题是一个数列或操作序列,先尝试不删去任何元素/操作定义状态,考虑对于每一种操作的转移方式,因为删去元素破坏了数组的连续性,通常会增加转移的复杂度。
dp 优化
数据结构优化dp
-
直接优化转移过程,即对于一个状态加速转移方程的计算,计算后插入 DS
-
将dp数组看成数据结构的维护对象,对其进行区间加,等等
用 DS 的目的是为了快速查找满足约束条件的状态,并打包信息
常见的约束条件就是下标,函数不等式,等等,注意枚举状态的循环通常会自动满足一部分下标要求(即下标小于当前状态)
两个视角
每个状态都有两个角色:
- 作为一个问题等待求解
- 作为一个子问题贡献给其他问题
通常就要从这两方面优化转移
一方面,作为一个待求解的问题,转移意味着固定一个范围进行查询
另一方面,作为一个子问题,你需要在求解完这个问题时将数据进行更新,使得后面的状态有信息可查,对应数据结构上的修改操作。
注意,当一个状态计算完成之后,它的值就显得不那么重要了,重要的是它在后续的转移当中有贡献的部分,这可能会在原状态基础上增加/减少一些数据。同时,这里不一定只修改当前状态的信息,也可能修改其他状态的信息。
例如,\(f(x) = f(y) + g(a[x, y])\),其中,\(g(a[x, y])\) 需要用到数组 \(a\) 从第 \(x\) 到 \(y\) 所有项,由于这些项对应的状态会在 \(f(x)\) 被计算之前考虑,所以就可以贡献到所有满足条件的 \(f(y)\) 上,从而 \(f(y) \rightarrow f'(y)\),转移时直接查询即可
例题:飞起玉龙三百万
序列上计数,考虑1D/1D DP
后效性处理(不局限于DP)
考虑调换计算顺序
凑数问题考虑背包
数位dp,不只是数位
“数位”的含义并不局限于普通的数值表示中的数位,即10进制,2进制等等各种进制下的数位,也可以表示某个数由多少个\(x\)构成(\(x\)是某整数),这可以理解为广义进制。
期望 dp
一般状态设为 到终点 的期望
数学
互质
定义
莫比乌斯函数(容斥)
欧拉函数 \(\varphi(x)\),定义为小于等于\(x\)的与\(x\)互质的数的个数
所有偶数都不互质,所有质数都互质
整除与同余
利用上面的式子,可以将带有取模的(不)等式与带取整号的式子相互转化,便于处理
CRT
若 \(a = \prod_{i=1}^n a_i\),且模数两两互质,那么
上/下取整
根据定义,有以下不等式
取整号内的整数可以提出来而不改变式子的值
上式对下取整同样成立
上取整与下取整的转化
Proof
根据取整的性质,有 \(a - 1 < \lfloor a \rfloor \le a\),则
移项,得
设 \(a - b= \lfloor a - b \rfloor + k,\ k \in [0, 1)\)
代入原式,得
由于左边是整数且 \(k \in [0, 1)\),有
同理,将 \((1)\) 式向另一个方向放缩,可以得到左式大于等于 \(0\)
当且仅当 \(k \ne 0\) 时等号成立,即当 \(a - b \in \mathbb{Z}\) 时,左式的值为 \(0\)
\(\forall a \in \mathbb{R}\)
将判定转化为计算
当发现某个量当条件为真时为 \(1\),否则为 \(0\) 时,可以对这个数求和,等价于判定
求和
Trick
这样可以交换求和顺序,可能便于加速计算(类欧)
交换求和顺序
然后可以解出 \(i\) 的范围,省去判定
排列 -> 置换与轮换
gcd
\(\gcd(a, b) = \gcd(a \bmod b, b) = \gcd(a-b, b)\) (当 \(a-b\)为定值 时,可考虑此式)
例:Interval GCD
涉及\(\gcd(a, b) = d\)的问题时,通常将\(d\)除掉,转化成互质问题
广义进制
专业术语叫变进制数
下面默认位数从第\(0\)位开始
通常意义下的进制有一个固定的基数,则每个整数可以表示成若干个基数的幂次的和,称为定进制数,即
但是每一位上的单位数值(\(100\cdots00\)在某一进制下的值)并不一定是简单的等比数列关系
设每一位的“进率”为\(c_i\)
则
其中每一位上的数\(a_i < c_i\)
当\(\forall c_i = k\)时,就是我们熟悉的定进制数
当\(b_i = i\)时,就是阶乘进制(康托展开):
也可以是有一个数列\(z = \{z_1, z_2, \cdots, z_n\}\)(这种是我自己编的,似乎不叫变进制数)
例如取\(z_i = 4z_{i-1}+1\)
这个数列应该具有类似“进位”的性质,即递推关系,从而每一位上的能填的数是有限的
并且,为了使得该进制下的数与十进制间建立双射,应当满足
其中\(a_i\)是第\(i\)为上能填的数的最大值
容易验证,上面所说的变进制数和定进制数满足这个条件(\(z_i = \prod_{i=0}^{i-1}c_i\))
但是下面这种基于数列定义的数可能不满足,所以需要进行一些特殊限制,如当某一位上的值达到最大后后面就必须填\(0\)等等
组合计数
分类
为补充不漏,可以考虑集合中的最大或最小元素进行分类
插板法与插空法
插空思想(插板法、插空法),当发现可插入位置与先前插入元素有关时,考虑变更插入顺序
CF1976E Splittable Permutations
CF1886D Monocarp and the Set
贡献法
贡献法的基本原理是:当需要计算一个总和(如所有子集的元素和之和、所有排列的逆序数之和等)时,可以将问题分解为每个单独元素对总和的贡献。具体步骤包括:
识别元素贡献: 确定每个元素在单个配置(如一个子集或一个排列)中的贡献值。例如,在计算所有子集的元素和时,每个元素在包含它的子集中贡献其自身的值。
计算贡献次数: 对于每个元素,计算它在所有可能配置中出现的次数。例如,在一个有 \(n\) 个元素的集合中,每个元素出现在 \(2^{n−1}\) 个子集中。
求和: 将每个元素的贡献值乘以其出现的次数,然后对所有元素求和,得到总和。
数学上,如果有一个集合 \(S=\{a_1,a_2,\cdots,a_n\}\),要计算所有子集的函数 \(f(T)\) 之和(其中 \(T\subseteq S\)),且 \(f(T)\) 可以表示为每个元素 \(a_i\) 的贡献之和,即:
则所有子集的 \(f(T)\) 之和为:
贡献法的优势在于将复杂的总和计算转化为每个元素的独立计算,从而降低问题难度。
排列上的贡献法
假设我们有一个函数 \(f(\pi)\),其中 \(\pi\) 是集合 \(S=\{a_1,a_2,\cdots,a_n\}\) 的一个排列。我们需要计算:
贡献法的步骤如下:
分解函数 \(f\): 将函数 \(f(\pi)\) 分解为更小、更基本的组成部分的贡献之和。这些组成部分通常是:
- 单个元素在特定位置上的贡献。
- 元素对 \((a_i,a_j)\) 之间的某种关系(如谁在谁前面、距离等)的贡献。
计算贡献次数: 利用排列的对称性和计数原理,计算每个组成部分在所有 \(n!\) 个排列中出现的次数。
求和: 将每个组成部分的“单位贡献值”乘以其“出现次数”,然后将所有组成部分的贡献加起来,得到最终的总和。
当然,有些时候可能也可考虑 答案 \(=(\) 所有 \(f(\pi) = k\) 的排列 \(\pi\) 的数量 \()\times k\)
以上内容来源于 Deepseek
CF1861E Non-Intersecting Subpermutations
模型:不定方程非负整数解的个数
求满足以下条件的解 \(x = \{x_1, x_2, \cdots, x_n\}\) 的个数,
利用插板法,容易得出答案是 \(s + k - 1 \choose k - 1\)
加入一些限制,给每个 \(x_i\) 加上下限
可以变形成这样,
我们将 \(x_i - a_i\) 看成新的未知数就和原来的问题一样了
还可以给 \(x_i\) 加上上界 \(r_i\),这个问题可以用容斥原理解决,也就是多重集组合数问题
我们设 \(S_i\) 为 \(x_i \le r_i\) 的方案,那么根据摩根定律 \(\overline{\bigcap S_i} = \bigcup \overline{S_i}\) 以及 \(|A| = |U| - |\overline{A}|\),答案就是
\(|U|\) 可以用普通的组合数算出来,后面的可以用容斥原理算,具体的,对于一个子集 \(T\),\(|\bigcup_{S_i \in T} \overline{S_i}|\) 表示的就是对于 \(\forall S_i \in T, x_i \ge a_i + 1\) 的方案数,套用第二种类型即可
继续变形的话我们可以给每个 \(x_i\) 加上一个系数 \(c_i\),即
这个就只能用 背包 求了
拆解组合对象
例如将选 \(pair\) 变成选点
博弈论
奇偶分讨,枚举策略
多项式
奇偶指数分治

浙公网安备 33010602011771号