笔记杂项
笔记杂项
用于记录一些难以归类的知识点或 trick
-
有 \(n\) 堆苹果,第 \(i\) 堆有 \(a_i\) 个相同的苹果。从这 \(n\) 堆中任选 \(k\) 堆(可以不选),从选择的每堆中取任意个苹果(至少取 \(1\) 个),求取苹果的总方案数。(两个取苹果的方案不同,当且仅当存在至少一堆苹果,两个方案从这堆苹果中取走的苹果数量不同。)
这是一个经典的组合问题,其解法非常简单:
每堆苹果有 \(0 \sim a_{i}\) 共 \(a_{i} + 1\) 种取法,总的方案数就是 \(\prod_{i = 1}^{n} (a_i + 1)\)。
上面这个式子的应用十分广泛。下面举一个例子:求整数 \(a\) 的因数个数。
对 \(a\) 进行质因数分解,得 \(a = \prod_{i = 1}^{l} p_{i}^{k_i}\),其中 \(l\) 是 \(a\) 的质因数的个数,\(p_i\) 是 \(a\) 的质因数。不难发现,\(a\) 的所有因数都可以表达为 \(a\) 的质因数相乘的形式,即\(\prod_{i = 1}^{l} p_{i}^{x_i}\),其中 \(0 \le x_i \le k_i\),例如 \(1\) 就是 \(p_{1}^{0} p_{2}^{0} \cdots p_{l}^{0}\)。对于每个 \(p_i\),它的指数可以从 \(0\) 到 \(k_i\),共 \(k_{i} + 1\) 种可能。因而总的因数个数就是 \(\prod_{i = 1}^{l} (k_i + 1)\)。
-
2024/2/26 21:50
\(n\) 个序列 \(\{p_1\}, \{p_2\}, \cdots, \{p_n\}\) 线性递推,每个序列的第 \(i\) 项和所有序列的前 \(k\) 项有关,可以构造出 \(nk \times nk\) 的转移矩阵。要计算到第 \(t\) 项,使用矩阵快速幂,时间复杂度为 \(O((nk) ^ 3 \log t)\)。
例题:SCOI2009 迷路
这道题很多人都是拆点做的,但是
我不会我们也可以不拆点,直接用递推去做。很显然,可以设 \(f_{t, i}\) 表示在 \(t\) 时刻到达点 \(i\) 的方案数,那么转移方程为 \(f_{t, i} = \sum_{e_{u, i} \not = +\infty \and e_{u, i} \le t} f_{t - e_{u, i}, j}\)。
注意到 \(e_{u, v} \le 9\),这就说明每个状态只与前 \(9\) 个状态有关,或者说对应于上文中的 \(k = 9\)。
可以把 \(t\) 视为转移到了第多少项,用 \(i\) 区分为不同的序列。这就可以用矩阵快速幂优化递推去做了。
-
2024/4/16 19:38
最小瓶颈树:最大边权最小的生成树。
最小瓶颈路:两点之间最大边权最小的路径。
性质:
-
最小生成树一定是最小瓶颈树,但反过来不一定。因此,要求最小瓶颈树,只要求最小生成树即可。
证明:运用反证法。假设最小生成树不是最小瓶颈树,那么最小瓶颈树中的所有边的边权都比最小生成树中的最长边小。删除最小生成树的最长边,则它断裂成两个连通块。在最小瓶颈树中,一定存在一条边可以连接这两个连通块,把这条边加入到最小生成树删完边的图中,它重新成为生成树。因为这条边的边权比删除掉的边小,所以新生成树的总边权比最小生成树小,矛盾。故得证。
-
最小瓶颈树上 \(x,y\) 的简单路径一定是 \(x,y\) 的最小瓶颈路。
应用:给出图 \(G\) ,多次询问点 \(x,y\) 的最小瓶颈路上的最大边权。可以这样做:先求出 \(G\) 的一棵 MST(它一定是最小瓶颈树),然后预处理 \(f_{u,k}\) 表示 \(u\) 向上到其第 \(2^k\) 个祖先中所有边的最大值。询问 \(x,y\) 时,求出他们的 lca,同时就可以得到答案。
-
-
2024/4/16 21:57
CF1245D 的建图方法很有启发性。题目本来提供了两种使城市通上电的方法:建发电站和修建一段通向有电城市的电路,这是不便于思考的。可以建立一个虚拟源点(“超级发电站”),把建发电站这一操作看作往这个超级源点连边,这样就把两种操作都统一为了连边的操作,于是就可以直接套用最小生成树算法,而且正确性显然。
-
2024/4/29 20:12
树上启发式合并
考虑这样一类树上问题:初始时有 \(n\) 个独立的节点,每次操作选择两个不在同一连通块中的节点,向它们之间加一条边(因为加边前它们不在同一连通块中,所以图一直是一个森林)。你还要对对一些询问做出回答,为了回答询问,需要维护一些东西(例如每个节点的父节点)。显然,每次合并,\(fa\) 都要被更新,而且更新的时间复杂度是线性的。那么怎样做才能使效率最高呢?
这里树上启发式合并(dsu on tree)就派上用场了。每次合并两棵树时,都要对其中一棵子树 \(O(n)\) 地扫一遍以维护 \(fa\)。这样做的最坏时间复杂度是 \(O(n^2)\) 的。而启发式合并的策略很简单:每次合并不是都要扫一棵子树吗?因为树是无根树,扫哪棵子树都不影响结果,那为什么不选择较小的那颗子树呢?这种策略确实很有效,它不是常数上的优化,而可以把时间复杂度降到 \(O(n \log n)\)。证明如下:
每次把小的块合并到大的块时,小的块的大小至少变为原先的两倍,所以最多扩大 \(\log n\) 次。而只有某个节点在小的块中时才会修改它的父节点,所以每个节点的父节点最多被修改 \(\log n\) 次。
实现
用一个并查集维护连通块的大小。每次合并,以所在连通块较小的那个节点为根 dfs 一遍,以重制这颗子树的 \(fa\) 数组。然后在图上加边,在并查集中合并两个块,并修改一下 size。
不要把并查集和图混淆了。虽然并查集确实是一个树形数据结构,但是由于在 getfa 时修改了边,所以不能用它来维护图上的关系,而只能维护每个连通块的信息。
-
2024/5/20
题目:P10511 方差
对于这类要分别处理块内+块外贡献的题目,要形成一套专门的写法,以方便自己调试。
首先对“块”要有一个认识。一般来讲,这类题目把序列分成了很多块,块内的元素具有相似性(比如本题中在同一块内的元素权值相等)。所以我们可以直接把块作为一个整体处理,以降低时间复杂度。直观来看是这样的:(随便举个例子,块用方括号区分)
\[\cdots \Box \Box] \textcolor{blue}{[\Box \Box \Box]} \textcolor{red}{[\Box \Box \Box \Box]} \textcolor{green}{[\Box \Box]} [\Box \Box \cdots \]对于要查询的的 \(x,y\) 的,定义以下变量:
\(lp\):从 \(x\) 往右,第一个块的下标。代码实现为
lp = upper_bound(l + 1, l + m + 1, x) - l
。\(rp\):从 \(y\) 往左,第一个块的下标。代码实现为
rp = lower_bound(r + 1, r + m + 1, y) - r - 1
。举个例子:
\[\cdots \dot\Box \Box] \overline{\textcolor{blue}{[^{lp}\Box \Box \Box]} \textcolor{red}{[\Box \Box \Box \Box]} \textcolor{green}{[^{rp}\Box \Box]}} [\dot\Box \Box \cdots \\ \]假设 \(x,y\) 分别是两个标点的方块,那么 \(lp\) 代表蓝色的块,\(rp\) 代表绿色的块。不难发现,这样定义的目的是使 \([l_{lp}, r_{rp}] \subseteq [x,y]\),于是就不会多算。
由于 \([l_{lp}, r_{rp}]\) 中的元素(上图中用横线标示)都在某个完整的块内,它们的贡献可以直接用前缀和算。剩下的元素只分布在两个块内,可以单独计算。
但是,如果 \(x,y\) 靠的比较近,可能会出现 \(lp > rp\) 的情况,这时需要特判。具体地,按以下三种情况分类讨论即可:
-
\(lp \le rp\)
这说明 \(x,y\) 之间至少”夹“了一个块:
\[\cdots ] \textcolor{blue}{[\Box \dot\Box \Box]} \overline{\textcolor{red}{[^{lp,rp}\Box \Box \Box \Box]}} \textcolor{green}{[\dot \Box \Box]} [\cdots \] -
\(lp - rp = 1\)
这说明 \(x, y\) 在相邻的两块内:
\[\cdots ] \textcolor{blue}{[^{rp} \Box \dot\Box \Box]} \textcolor{red}{[^{lp}\Box \dot\Box \Box \Box]} [\cdots \] -
\(lp - rp = 2\)
这说明 \(x,y\) 在同一块内:
\[\cdots ] \textcolor{blue}{[^{rp} \Box \Box \Box]} \textcolor{red}{[\dot\Box \Box \Box \dot\Box]} \textcolor{green}{[^{lp} \Box \Box]} [ \cdots \]
-