动态规划做题记录-2
AGC028D. Chords
考虑断环为链,那么两个线段相交当且仅当他们仅相交不包含。我们用 \((l,r)\) 表示一个连通块当且仅当 \(l,r\) 是一个连通块的两个端点,也就是最小点和最大点。那么我们考虑枚举每一个连通块 \((l,r)\),考虑它们对答案的贡献,也就是这个连通块在多少种方案中存在,令 \(f_{l,r}\) 表示这个值。
注意到,如果 \((l,r)\) 是一个连通块,那么 \(r-l+1\) 应为偶数,并且其中的所有点不能向外有连边,需要先特判一下。否则我们考虑在整个区间内随意连边的方案数 \(g_{c(l,r)}\),其中 \(c(l,r)\) 是 \([l,r]\) 中没有连边的点的个数,\(g_x\) 当 \(x\) 为奇数时等于 \(0\),否则等于 \(\prod_{k=1}^{\frac{x}{2}}(2k-1)\)。然而我们随意连边 \([l,r]\) 不一定成为连通块,这时候可以进行容斥,将 \(l\) 和其他点形成连通块的方案数减去,那么我们有转移方程:
可以在 \(O(n^3)\) 的复杂度内完成转移,统计答案即为 \(\sum f_{l,r}g_{2n-2k-c(l,r)}\)。
AGC022F. Checkers
考虑让 \(A\) 关于 \(B\) 对称变成一条 \(A\to B\) 的边,那么因为进行了 \(n-1\) 次操作,于是我们得到了一棵树。考虑一次对称后的坐标为 \(2x_{B}-x_A\),最多 \(n-1\) 次对称支持某一个坐标前的系数达到 \(2^{n-1}\),远不及 \(10^{100}\),因此最终的坐标是否相同仅与每个位置前的系数相关。
考虑对于一棵树而言,我们能够轻易得出每个位置前的系数的绝对值为 \(2^{\text{dep}}\),而较为困难的是正负号,从而我们可以考虑以这个内容入手解决。对于正负号而言,其不仅与儿子节点的个数有关,还与对称的顺序有关,然而在父亲节点往上其和父亲接受的取反是同时的,于是我们考虑维护每个位置和其父亲对答案贡献的正负性是否相同。
发现对于一个有 \(\text{son}\) 个儿子的节点,共有 \(\lfloor\frac{\text{son}}{2}\rfloor\) 个点与其取反相同(忽略儿子的儿子对儿子的影响)。于是我们设 \(f_{i,j}\) 表示选择了 \(i\) 个点且最后一层有 \(j\) 个有奇数个儿子的点。接着我们枚举这一层节点的个数 \(k\),那么因为每一个奇数节点的点会损失一个儿子使得取反相同,那么总共和父亲取反相同的点的数量 \(t\) 为 \(\frac{k-j}{2}\),如果 \(k-j\) 为奇数,则肯定不合法,不予考虑。接着我们关注这一层有多少个节点有奇数个儿子。考虑枚举这一层有多少个节点和父亲对答案位置的贡献的正负号是相同的,假设为 \(p\),那么说明这一层至少有 \(|t-p|\) 个奇数个儿子的节点。而更多的节点是没有意义的,因为我们总是选取多余的偶数个奇节点合并到剩下的奇节点上使得对答案的贡献不变,那么我们只需要考虑对 \(|t-p|\) 的转移即可。于是我们又转移方程
于是我们可以做到 \(O(n^4)\) 的复杂度。
UOJ607.【UR #20】跳蚤电话
考虑什么样的方案才会得到最后的配对方案。我们发现第二种操作在树上相当于覆盖了一条简单路径,因为我们肯定可以通过第一种操作不断补齐这条链路径在树上的每一条边,具体操作就是选择一条边 \((u,v)\),在 \(u,v\) 之间的简单路径上选择一个点 \(w\),对这三个点操作,可以将连边改为 \((u,w)(w,v)\),这样不断细化一定可以配对路径上的所有边。也就是说,如果存在恰好一条路径覆盖了这条边,那么这条边一定会在最后刚好有一个配对。然而如果存在多条路径覆盖这条边,说明这条边被多次配对,这显然不合法。通过进一步分析我们发现,在树上连边的任意时刻,这个树总是不存在横叉边。否则考虑边 \((u,v)\) 是横叉边,因为 \((u,v)\) 能够存在,说明两者其中一个点到根的链已经被覆盖,如果再将 \(u,v\) 之间的路径覆盖,那么显然有一条边会被覆盖两次,这显然不合理。
通过上面的过程,我们将操作改写,初始时 \(1\) 既被覆盖也被选择:
- 选择一个已经被覆盖但未被选择的点,将其的状态设为被选择。这相当于在进行原来的第二种操作。
- 选择一个被选择的点,然后选择一个子树内的点,使得两个点之间的简单路径上没有任何点被覆盖(除子树的根节点),将这条路径上的点的所有状态设为被覆盖,将选择的点的状态设为被选择。这相当于在进行原来的第一种操作。
那么题目本质上想要询问将所有点全部选择的方案数。继续分析我们发现,对于所有点来说,在每一种情况下,只可能有一种将状态设为被选择的方案(如果未被覆盖则只能通过改写的第二个操作,否则只能通过改写的第一个操作),那么我们可以只关心每个点被选择的顺序序列 \(p\),那么我们仅需得到合法的 \(p\) 的数量。
继续思考,我们发现在一个子树的根节点被选择前,这个子树内一定最多只有一个子树的点被选择。我们从这一点入手开始 dp:设 \(f_{u}\) 表示在 \(u\) 的子树中将所有点均选择的方案数,\(\text{siz}_u\) 为 \(u\) 的子树的大小。那么会有以下两种可能。
-
先选择了根节点。此时这个子树的 \(p_1\) 一定是 \(u\),操作所有剩下所有子树的顺序互不干扰,就是可以在保证每个子树原来 \(p\) 顺序不变的基础随意排列。那么总方案数为
\[{\text{siz}_u\choose\text{siz}_{\text{son}_1},\text{siz}_{\text{son}_2},\cdots,\text{siz}_{\text{son}_k}}\prod f_v \] -
先选择了某个子树,假设为 \(\text{son}_i\),那个在下一个子树的点出现之前一定会加入 \(u\),因此剩下的子树的操作方案数为上面的式子去掉 \(\text{son}_i\) 的贡献。然后考虑操作 \(\text{son}_i\) 和其他子树的顺序,显然在操作完 \(\text{son}_{i}\) 的第一个点后,剩下的点可以在顺序不变的情况下乱排,那么总方案数就是
\[\begin{aligned}&{\text{siz}_u-\text{siz}_{\text{son}_i}\choose \text{siz}_{\text{son}_i}-1}f_{\text{son}_i}\times{\text{siz}_u-\text{siz}_{\text{son}_i}\choose\text{siz}_{\text{son}_1},\cdots,\text{siz}_{\text{son}_{i-1}},\text{siz}_{\text{son}_{i+1}},\cdots,\text{siz}_{\text{son}_k}}\prod_{v\ne\text{son}_i} f_v\\=&\frac{\text{siz}_{\text{son}_i}}{\text{siz}_u-\text{siz}_{\text{son}_i}}{\text{siz}_u\choose\text{siz}_{\text{son}_1},\text{siz}_{\text{son}_2},\cdots,\text{siz}_{\text{son}_k}}\prod f_v\end{aligned} \]
那么可以直接枚举所有儿子算出贡献相加得到 \(f_u\),注意 \(f_1\) 只有第一种贡献,复杂度是 \(O(n)\) 的。
UOJ181.【UR #12】密码锁
考虑到竞赛图缩点之后一定是一条链,考虑到这条链上的前缀的点集一定没有入边,而链上强连通分量的个数和前缀的个数一样,我们从这一点入手可以给出一个 \(O(n^22^n)\) 的算法,只需要暴力枚举所有点集然后接着暴力枚举所有边统计这个点集是一个前缀的概率。由于期望的线性性,只需要将所有点集的概率简单相加即为答案。
考虑这个算法的优化,我们发现除了 \(m\) 条边以外的所有边的边权均为 \(\frac{1}{2}\),也就是说假如这个点集大小为 \(i\) 且向外没有特殊边,那么贡献一定是 \((\frac{1}{2})^{i(n-i)}\),否则一条概率为 \(p\) 的特殊边的贡献是 \(2p\),我们只需要对于每一个子集枚举所有特殊边判断其是否对概率有贡献即可,复杂度优化到 \(O(m2^n)\)。
考虑相对而言 \(m\) 较小,我们尝试将 \(m\) 挪到指数的位置上。因为特殊边只会对包含其中某些点的点集作贡献,我们不妨考虑枚举所有特殊边的连通块,这样我们只需要知道点集包含其中的哪些点即可做出贡献。我们枚举这个弱连通块内的点是否选择的状态,然后枚举所有特殊边统计贡献。因为这个连通块之前没有选择过,因此只要选择当前的状态,就一定会多选择 __builtin_popcount
个点,那么我们可以进行一个背包 dp,统计出所有大小为 \(i\) 的点集的贡献和,最后统一贡献给答案。因为 \(m\) 条边的连通块最多有 \(m+1\) 个点,所以这样复杂度是 \(O(n^22^m)\) 的。
但是你可以考虑在对于连通块进行背包 dp 的过程中,我们可以将贡献统一延后计算,也就是在枚举完所有状态后,对所有新增个数的贡献背包,那么复杂度可以降到 \(O(n2^m)\)。
UOJ370.【UR #17】滑稽树上滑稽果
考虑有根树纯粹在扯淡,因为你构造出一条链肯定更优,于是我们考虑如何构造一条最优的链。考虑所有 \(a_i\) 的 \(\text{and}\) 和 \(A\),我们肯定可以通过最多 \(\log w\) 个点使得 \(\text{and}\) 和达到 \(A\),所以我们不妨考虑在什么时候前缀的 \(\text{and}\) 和为 \(A\)。设 \(f_{i}\) 表示前 \(i\) 个位置的元素的 \(\text{and}\) 和为 \(A\) 的最小代价,那么答案即为 \(\min\{f_i+(n-i)A\}\)。具体维护可以考虑转移 \(g_{i,S}\) 表示前 \(i\) 个元素的 \(\text{and}\) 和为 \(S\) 的最小代价,这个可以轻松做到 \(O(nV\log V)\)。有一个经典的内容是我们可以将代价放到 dp 里面计算,就是预先提供后面的代价,有新的代价时将后面的代价替换,这样可以做到 \(O(nV)\)。
我们考虑不要枚举每一个元素去转移,而是考虑每一个状态之间的转移是否可行。更具体的,考虑到 \(S\) 总是转移到自己的子集,我们考虑枚举所有 \(S\) 的子集 \(T\),那么能够转移到 \(T\) 的必要条件是存在一个 \(S\oplus T\oplus U\) 的子集的元素(\(U\) 是全集),这一点可以通过高维前缀和做到 \(O(V\log V)\)。但是我们其实不关心是否能恰好转移到正确的位置,因为我们转移到一个错误的位置一定不优,而正确的位置也一定会被转移到,所以正确性是没有问题的,复杂度是 \(O(3^{\log V})\)。
UOJ312.【UNR #2】梦中的题面
首先考虑对于 \(m\) 个数,每个数在 \([0,n]\) 之间,要求和 \(\le n\) 的方案数。这个不难通过组合数算出是 \({n+m\choose m}\) 的。接着考虑对于这个问题特殊的上界要求,我们选择进行容斥,即钦定一定超过上界的数的集合 \(S\),通过二项式反演可以得到答案即为
我们通过这一点进行 dp,通过 Vandermode 卷积我们有
于是我们可以拆分贡献,将大的组合数拆分到每一个被选择在 \(S\) 的集合中的元素去统计答案,即令 \(f_{i,k}\) 表示前 \(i\) 个元素在组合数底数为 \(k\) 意义下的答案和,答案即为 \(f_{m,m}\)。但是考虑我们如果需要利用卷积去统计答案,则需要用到实数域的组合数,即顶数为负数的组合数,但是在原式中,\(n-\sum_{i\in S}b^i+(c-1)|S|<0\) 时这个式子没有贡献,然而在实数域里,我们会统计它的贡献。因此我们需要保证选择的 \(S\) 满足 \(\sum_{i\in S}b^i-(c-1)|S|\le n\)。考虑用数位 dp 去限制这个内容,即考虑从高到低将能否选择某个数超过限制变成一个二进制数,并要求字典序最大。这样之后一个合法的二进制状态一定满足其不超过这个上限状态,因为后面所有数的和都不会比当前这个大。那么我们新开一位表示当前的选择是否顶满上界,然后从大到小枚举每一位转移即可,复杂度是 \(O(m^3)\) 的。至于快速统计上限状态,我们可以将 \(n\) 转成 \(b\) 进制数,然后判断减去某个上界后是否变为负数即可。