qbxt 刷题班动态规划笔记
状压
solution
老题了,状压每个行的状态,枚举前两行转移就好了。
solution
\(dp\) 状态很显然,就是设 \(f[s]\) 表示点集 \(s\) 内所有点联通的方案数。
怎么求联通的方案数?
统计一个集合中所有点联通的方案数很难,但是可以用所有的连边情况 - 不连通的方案数的方案数就可以求出来了。
所有的连边情况:\(g[s] = \prod (c_{i,j} + 1)(i, j\in s)\) (两点之间可以不选边,所以要+1)
求不联通的的方案数?真的妙 = =
一个错误思路:枚举一个联通块 \(t\),然后剩下的点可联通可不连通,这样就保证了这个联通块和其余点之间没有边,达到了总的图不连通。方案数为: \(f[t]\times g[s - t]\) ,然后把枚举的答案相加就好。
这样可能会算重,因为枚举完一个联通块的时候计算一个方案,枚举另一个连通块的时候有可能会出现相同的情况,也会被记录答案。
枚举一个点所在的连通块,确定一个点 \(p\) ,对于每种不同的方案,\(p\) 点只可能在一个联通块内,枚举这个联通块 \(T\) ,那么不连通的方案就是 \(\sum f[T]\times g[s - T]\)
转移式子就有了:\(f[s] = g[s] - \sum f[T] \times g[s - T]\)
solution
很好的一道 \(dp\) 题,涉及到状压和序列。
拿书和放书是独立的。
放的时候很简单,就是找高度相同的把它们放在一起,如果没有相同就放在两端就好了。
考虑怎么拿,使得拿完之后段数最少。
\(f_{i,j,x}\) 表示考虑到第 \(i\) 本数,拿了 \(j\) 本,上一本留下来的书的高度为 \(x\) 的最少段数,转移枚举当前这本书拿不拿就好了。
发现这样设状态的话,放书产生的贡献就没法计算了,改一下状态。
设 \(f_{i,j,x,s}\) ,\(s\) 表示剩下的书的高度的集合,那么产生的贡献就是 \(n\) 本书的高度集合 xor 剩下的书的高度集合,就是放书产生的贡献。
solution
读题可知:令 \(i < j\) ,如果从 i -> j 代价就是 \(j - i\) ,如果从 \(j\) 传到 \(i\) ,那么代价就是 \(k(i + j)\) 。
\(O(n)\) 预处理一个 \(c_{i, j}\) 表示 i->j 信号传递了多少次,
最后的答案就是求所有的 \(c_{i, j}\times (j - i) + c_{j,i}\times(i + j)k\)
首先可知直接枚举 \(m\) 这个排列,复杂度为 \(O(n + (m!)\times m^2 )\)
因为 \(m\) 很小,考虑状压 \(dp\)
设 \(f[s]\) 表示考虑了前 \(|s|\) 个位置,填了 \(s\) 里面的这些数。
考虑转移。
当枚举下一个位置的时候,\(pos = |s| + 1\) 个位置的时候,枚举新填的数,这个新填的数 会和集合中的数和集合外的数(不等于 i 的数)产生代价,
- 对于一个前面的数 \(j\) ,从 \(i\) 到 \(j\) 产生的代价是:\(\text{pos}\cdot k\cdot \text{cnt}[i][j]\)
- 对于一个前面的数 \(j\) ,从 \(j\) 到 \(i\) 产生的代价是:\(\text{pos}\cdot\text{cnt}[j][i]\)
- 对于一个后面的数 \(j\),从 \(i\) 到 \(j\) 产生的代价是:\(-\text{pos}\cdot \text{cnt}[i][j]\)
- 对于一个后面的数 \(j\),从 \(j\) 到 \(i\) 产生的代价是:\(\text{pos}\cdot k\cdot \text{cnt}[j][i]\)
转移式子就是
\(dp[s + i] = dp[s] + \sum_{j\in s} pos(k\cdot cnt[i][j] + cnt[j][i] ) + \sum_{j\notin (s + i) }pos(-cnt[i][j] + k\cdot cnt[i][j])\)
枚举 \(i\) ,再枚举 \(j\) ,复杂度就是 \(O(2^mm^2)\)
优化:
枚举 \(i\) ,把 \(i\) 在 \(pos\) 之前的贡献预处理出来,记作 \(cost_{s, i}\),那么上面转移就不用再枚举那个 \(j\) 了,转移就可以 $2^m\times m $ 的实现。
[ATcoder] Pay to Win
进行若干操作将 \(0\) 变为 \(N\) ,\((N\leq 10^{18})\) 。
- 花 \(A\) 个金币把数字 \(\times\) 2
- 花 \(B\) 个金币把数字 \(\times\) 3
- 花 \(C\) 个金币把数字 \(\times\) 5
- 花 \(D\) 个金币把数字 \(+1\) 或者 \(-1\)
求最小需要花费的金币。
solution
当 \(N\leq 10^6\) 时,考虑 \(dp\) 转移。
设 \(f_i\) 表示将 \(i\) 拼成 \(N\) 的最小代价,这时候加减法的转移就有后效性,并且转移没有边界
考虑这么一件事情,如果这个乘了一个数,那么之后要加的数一定比这个数小。
例如: 2 * 5 + 10 这样会花费 \(5C + 10D\) 的代价,显然不如 \((2 + 2)\times 5\) 更优。
就可以把所有的加操作都放在前面,最后就省乘的转移。
设加完之后数为 \(i\) ,那么它可以转移到
i -> 2*i - 1,2 * i,2 * i + 1,3 * i - 2, 3 * i - 1, 3 * i,3 * i + 1, 3 * i + 2......
然后就可以从后向前 \(dp\) 了。
现在考虑 \(N\leq 10^{18}\)
\(ai + b = c (b < a)\) 那么 \(i = \lfloor \frac{c}{a} \rfloor\)
\(ai - b = c(b < a)\) 那么 \(i = \lceil \frac{c}{a} \rceil\)
这样就可以快速的考虑每个点是由那几个状态转移过来的了。
solution
枚举一个起点 ,设 \(f_{s}\) 表示加入的点集为 \(s\) ,\(s\) 内的点全部互通的最小代价,\(st_{s,j}\) 表示加入的点集为 \(s\) ,到达 \(j\) 点经过的点的个数。
这样就可以枚举一个集合内的点合集合外的点进行转移。
\(st_{s,j}\) 每次转移都要先复制原来的状态,再进行转移。
树形
对于一棵 \(N (N ≤ 300000)\) 个点的树,我们可以将某条链和与该链相连的边抽出来,看上去就象成一个毛毛虫。
求点数最多的毛毛虫。
solution
类似树形 \(dp\) 求树的直径,\(f_u\) 表示以 \(u\) 为根节点,其子树内最长的毛毛虫,转移就很简单了
剩下的和求 \(dp\) 求树的直径就一个样了。
P1472 [USACO2.3]奶牛家谱 Cow Pedigrees
一个有 \(n\) 个节点,深度为 \(k\) 的无标号完满二叉树(即每个节点的儿子数为 0 或 2)有多少种结构?定义根节点深度为 1。
答案对 9901 取模。
solution
\(f_{i, j}\) 表示 \(i\) 个点,深度为 \(j\) 的方案数。枚举左右子树的节点个数和深度,这样复杂度就到了\(n^4\)。
不妨设 \(f_{i, j}\) 表示 \(i\) 个节点,深度小于等于 \(j\) 的方案数,那么答案就为 \(f_{i, j} - f_{i, j - 1}\)
转移就为 \(f_{i, j} = f_{t, j - 1} + f_{i - t - 1, j - 1}\)
给定一棵 \(n\) 个节点的树,并且每条边都有权值。现在有一个点向周围发射信号,使得以这个点为根,到达叶子节点的路径权值相同。
\(n\leq 5\times 10^5\)
solution
以发射信号的节点为根,因为要求到达每个叶子结点的路径权值都相等,所以这个树的每个子树同样也满足这个性质。
反过来思考,所有叶子结点到达每个子树的根的路径长度也同样相同。
所以就可以从叶子结点向上维护到根的距离就好了。
\(n\) 个点 \(n\) 条边的单圈图,保证环上任意两点都只有两条路经互通,在上面做没有上司的舞会问题。(在基环树上求最大的独立集)
\(n\leq 10^5\)
solution
先写一边没有环的情况
\(f_{u, 0} = \sum max(f_{v, 1}, f_{v, 0})\)
\(f_{u, 1} = \sum f_{v, 1}\)
如果有环就找环上相邻的两个点 \(S , T\),它们一定不会同时选,把它们之间的边断掉,然后分别以 \(S, T\) 为根,跑一遍树形 \(dp\) ,找环上两点用并查集判断,最后答案 \(min(f_{s, 0}, f_{T, 0})\)
给一棵树,你可以匹配有边相连的两个点,问你这棵树的最大匹配是多少,并且计算出有多少种最大匹配。
\(N\leq 10^5\)
solution
\(f_{u, 0|1}\) 表示 \(u\) 这个结点它与儿子结点形成不形成匹配,其子树内的最大匹配数量。
\(f_{u, 0} = \sum max(f_{v, 0}, f_{v, 1})\)
当该点与一个儿子形成匹配时,该儿子状态为 \(f_{v, 0}\) ,其余可以随便选。
这时,找一个 \(\Delta = f_{v,0} - max(f_{v, 0}, f_{v, 1})\) 最大儿子形成匹配就好了。
此时 \(f_{u, 1} = f_{u, 0} + 1 + \Delta\)
方案数?
先考虑没有最大的限制,求一棵树有多少种匹配。
\(f_{u,0|1}\) 表示 \(u\) 这个结点它与儿子结点形成不形成匹配,其子树内的匹配方案数。
\(f_{u,0} = \prod f_{v, 1} + f_{v, 0}\)
\(f_{u,1} = \sum \frac{f_{v, 0}}{f_{v, 1} + f_{v, 0}}\times f_{u, 0}\)
最大匹配的方案数
$f_{u, 1|0} $ 表示 \(u\) 这个结点它与儿子结点形成不形成匹配,其子树内的最大匹配方案数。
同上面求方案数一样,但是 \(f_{v,0}\) 和 \(f_{v, 1}\) 就不能任意取了,因为 \(v\) 点选不选可能它子树内的最大匹配数会不同,我们只需要它最大匹配数的状态。
\(g_{u, 0|1}\) 表示 \(u\) 这个结点它与儿子结点形成不形成匹配,其子树内的最大匹配数量。
-
\(g_{v, 0} > g_{v, 1}\) 取 \(f_{v, 0}\)
-
\(g_{v,1} > g_{v,0}\) 取 \(f_{v, 1}\)
-
\(g_{v, 1} = g_{v, 0}\) 取 \(f_{v,1} + f_{v,0}\)
所有取到的状态相乘就好了。
背包
solution
每个城堡是独立的,所以可以单独计算每个城堡可以打败多少贡献。
设 \(f_{i, j}\) 表示前 \(i\) 个城堡排出 \(j\) 个士兵的最大得分。
贪心排出的士兵数恰好比一个玩家的两倍大,多了也是浪费 = =
预处理每个城堡的敌人排出的兵,对其排个序
同时把第一维滚掉就为
\(f_j = max(f_j, f_{j - 2\times a[i][k] - 1} +k\times i)\)
solution
不缺少物品时候的转移为
for (int j = m; j >= w[i]; --j)
f[j] += f[j - w[i]];
当删除一个物品的时候,就把这个物品转移过来的状态全部删掉就好了。
memcpy(g, f, sizeof f);
for(int j = w[i]; j <= m; ++j)
g[j] -= g[j - w[i]];
给定一棵 \(n\) 个结点的树,现割掉一些边,使得某一块连通块的大小恰好为 \(p\) 。
\(n\leq 150\)
solution
树上背包,设 \(f_{u,s}\) 表示 \(u\) 所在的联通块,大小恰好为 \(s\) 的最小代价。
这样就是把这 \(s - 1\) 恰好分配到它的每个儿子,使得总代价最小。
如果只有两个儿子的时候
\(f_{u,s} = min_{j\in[0, s - 1]}(f_{v_1,j}, f_{v_2, s - j - 1}) + (j == 0)|(s - j - 1 == 0)\)
特殊的 \(f_{u, 0} = 1\)
考虑多个儿子合并,再填上一维状态。
\(f_{u, k, s}\) 表示考虑节点 \(u\) 的前 \(k\) 个子节点了,大小为 \(s\) 的最小花费。
转移就是
其中 \(k_v\) 表示 \(v\) 的所有子节点数量,\(s_v\) 表示在子树 \(v\) 上选取的点的数量。
前面表示割掉 \(v\) 这个儿子,后面表示在 \(v\) 选一定的点。
滚掉 \(k\) 就成了
这时候 \(s\) 要倒叙枚举,因为每次转移都要用到 \(f_{u, s - s_v}\) 在上一个儿子时的状态,把它放在后面更新就好可以了。
关于答案:显然不能直接输出 \(f[1][p]\) 因为 \(1\) 号节点不一定选,对所有的 \(f[i][p]\) 取个 \(min\) 然后 +1(要断开父亲那条边)
小 \(y\) 有一个大小为 \(N\) 的背包,并且小 \(Y\) 有 \(N\) 种物品。
对于第 \(i\) 种物品,共有 \(i\) 个可以使用,并且对于每一个 \(i\) 物品,体积均为 \(i\) ,求把背包装满的方案数。
方案不同:当且仅当至少存在一种物品的使用数量不同。
\(N\leq 10^5\)
solution
\(f_{i, j}\) 表示前 \(i\) 个物品,体积为 \(j\) 的方案数。
\(f_{i, j} = \sum f_{i - 1, j - i\times k}\)
复杂度 \(O(n^3)\)
优化:前缀优化。
转移都是从前面 \(i\) 的倍数转移过来的,把 mod i 相同的分到一组,预处理一个前缀数组 \(g_{i - 1, j} = f_{i - 1, j -i} + f_{i - 1, j - 2\times i}\dots\)
这样就实现了 \(O(n)\) 的转移
给定正整数 \(n,m\) ,要求计算 \(\text{Concatenate}(n) \bmod \ m\) 的值,其中 \(\text{Concatenate}(n)\) 是将 \(1 \sim n\) 所有正整数 顺序连接起来得到的数。
solution
这主要是学到了一种写矩阵快速幂的方法。
先递推式子。
\(f_{i + 1}\) 表示前 \(i + 1\) 个数组成的数。
那么递推式为 \(f_{i + 1} = f_{i} \times 10^k + i + 1\) ,其中 \(k\) 为 \(i + 1\) 的位数。
如果 \(k\) 固定,可以写矩阵。(把推到得状态写在下面,原状态在左边)
这么一个框架:
1: 1 1 1 1 1 1
i: 0 1 1 -> [1, i, f(i)] * 0 1 1
f(i):0 0 10^k 0 0 10^k
1 i+1 f(i+1)
然后这个 \(k\) 其实并不固定,所以可以把 \(k\) 相同得放在一个矩阵里,分组进行就好了。
序列
solution
因为 \(a\) 从前向后插入的时候只能加在 \(b\) 序列的最左边或最右边。
那么 \(a\) 的一个前缀一定对应着 \(b\) 序列中的一段连续的区间。
设 \(f_{i, j,0|1}\) 表示能形成 \(b\) 序列区间 \(i\sim j\) ,最后加入的数在左边还是右边的 \(a\) 序列前缀的方案数。
枚举边界进行转移就好了。
solution
\(f_{i, j}\) 表示区间 \(i\sim j\) 折叠之后的最短长度。
考虑转移。
两种情况:
- 总串是由若干个子串拼起来的,枚举断点。
- 总串是由一个串重复若干次的来的。
其中 2 是两个括号的长度,\(g(k)\) 表示这个字串折叠后的最短长度。
同时 \(k\) 要满足是 \(r - l + 1\) 的因子,同时要判断枚举的字串是否能重复得到该区间。
暴力判断就好了。
solution
线段树优化 \(dp\) 转移
\(f_{i,j}\) 表示执行了 \(i\) 次操作,两个点的分别在 \(x_i\) (其中一个点一定会在 \(x_i\))和 \(j\) 的最小代价。
转移:
\(f_{i + 1, j} = min(f_{i, j} + |x_i - x_{i + 1}|)\)
\(f_{i + 1, x_i} = min(f_{i, j} + |j - x_{i + 1}|)\)
优化:
线段树滚掉第一维
以 \(j\) 为下标,\(f_j\) 为权值建一棵线段树,对于第一个操作就是全局 \(+|x_i - x_{i + 1}|\)
对于第二个操作,分类讨论把绝对值拆开,就成了:
问题就变成了查询区间 \([j, n] (j > x_i)\) 中的 \(f_{i, j} + j\) 和查询 $[1, j](j < x_i) $ 中 \((f_{i, j} - x_{i + 1})\) 的最小值了。
所以在线段树上只需要维护 \(f_{j} + j\) 和 \(f_j - j\) 就好了。
复杂度:\(O(n + Q logn)\)
CF713C Sonya and Problem Wihtout a Legend
给定一个有 \(n\) 个正整数的数组,一次操作中,可以把任意一个元素加一或者减一。(元素可被减至负数或 \(0\)),求使得原序列严格递增的求最小操作次数。
solution
严格单调递增转化为非严格单调递增(如果不转化没法对 \(a_i\) 离散化):
\(a_i < a_{i + 1}\)
得到 \(a_i \leq a_{i + 1} - 1\)
两边同时减 \(i\) : \(a_i - i \leq a_{i + 1} - (i + 1)\)
另 \(b_i = a_i - i\)
那么上式就可以转化为 \(b_i \leq b_{i + 1}\)
可以得到这么个结论:将 \(a_i - i\) 那么序列就会成为一个非严格单调递增的。
可以先求出一个非严格单调递增的最后都加上 \(i\) 就可以了。
考虑 \(dp\) 怎么实现
设 \(f_{i, j}\) 表示前 \(i\) 个数,第 \(i\) 个数为 \(j\) 的最小代价。
转移: \(f_{i, j} = |b_i - j| + min_{k\leq j}(f_{i - 1, k})\)
后面那一项是个前缀最小值,可以直接预处理出来。
另 \(g_{i, j} = f_{i, j} + g_{i, j - 1}\)
转移就成了 \(f_{i, j} = |b_i - j| + g_{i - 1, j}\)
有 \(n\) 个数,每个数权值为 \(a_i\) ,现在要把他们划分成 \(m\) 段,求每段权值之和最大值最小是多少,并求出在该情况下划分的方案数。
\(n\leq 50000\),\(0 \leq m \leq min(n - 1, 1000)\),\(1\leq a_i\leq 1000\)
solution
第一问直接二分答案就好了。
第二问,根据第一问二分的答案 \(x\) 划分。
令 \(f_{i, j}\) 表示前 \(i\) 个数划分了 \(j\) 段的方案数。
转移:\(f_{i, j} = f_{i,j} + \sum^{k \leq L_i}_{k = 1}f_{i - k, j - 1}(\sum^{c = i}_{c = i- L_i+ 1} a_c \leq x)\)
复杂度: \(O(m\times n ^ 2)\)
考虑优化:
\(\sum^{k \leq L_i}_{k = 1}f_{i - k, j - 1}(\sum^{c = i}_{c = i- L_i+ 1} a_c \leq x)\) 这一坨玩意其实是个区间和,可以用前缀和搞一搞
令 \(sum_{i, j} = \sum^{k\leq j}_{k = 1} f_{i, k}\)
转移式子就可以写成 \(f_{i, j} = sum_{i - 1, j} - sum_{i - 1, k - 1}\)
这样其实复杂度一点没变,因为还要枚举一个 \(k\),以为 \(sum\) 满足单调性,所以可以直接用双指针处理出每个点前面的 \(k\) 在哪儿就好了。
复杂度:\(O(nm + nlog(n\times a_i))\)
生成一个长度为 \(n\) 的排列,给定排列的第一个和最后一个元素,同时要求该排列的每个数至少都满足以下的一条性质:
- \(a_{i} < a_{i + 1}~ \&\&~a_{i} < a_{i - 1}\)
- \(a_i > a_{i + 1}~\& \&~a_i > a_{i - 1}\)
求有多少种合法的排列 ?
一道奇妙的 \(dp\) 题。
solution
插数 \(dp\)。
我们可以从小到大向序列中插数。
状态:\(f_{i, j}\) 表示插到了第 \(i\) 个数,将序列分成了 \(j\) 段,并且每一段都符合条件的方案数。
转移:
- 当 \(i\neq s~\&\&~i\neq t\)
\(f_{i, j} = (j - (i > s) - (i > t)) \times f_{i - 1,j - 1} + j\times f_{i - 1,j + 1}\)
当插入的数不是起点或终点的时候。
插入的数可以插入原来两段之间把两段连起来,也可以单独成段。
注意: 当插入的数比起点大的时候不能插入到最左边,因为数是从小到大插入的,如果比起点大证明起点已经插入了,如果比终点大的时候同理也不能插入到最右边。
- 当 \(i = s~\or~i=t\)
\(f_{i, j} = f_{i - 1, j - 1} + f_{i - 1, j}\)
当插入的时候是起点或终点的时候。
插入的数只能放在两边,可以单独成段,也可以与已有的段合并。
注意转移的时候段数,很容易出错。
solution
读懂题太不容易了 /kk
把操作放在图上,移动的轨迹就是一个个的环,每个奶牛都在某个环上移动,若操作次数是某个环长的倍数,那么这个环上的奶牛都会回到起点,如果想让所有奶牛都回到起点,就是所有环长度的倍数。求操作次数最少,就是求每个环的最小公倍数。
假如设每个环的长度是 \(a_i\),那么可以发现 \(k=\operatorname{lcm}a_i\) ,又知道 \(\operatorname{lcm}\) 其实就是将那几个数分解质因数,然后取每个质数的最高次幂乘起来,所以我们可以想到枚举素数来做 \(dp\)。
我们设 \(f_{i,j}\) 表示前 \(i\) 个素数总和为 \(j\) 的所有 \(k\) 的总和,枚举第 \(i\) 个素数的幂进行转移,因为之前并没有用过第 \(i\) 个素数,所以应把上一个状态乘上 \(p_i^k\) ,所以直接有方程 \(f_{i,j}=\sum f(i-1, j-p_i^k)\times p_i^k\)
接着发现这个东西可以滚动数组压缩一下,于是可以省掉一维 \(f(j)=\sum f(j-p_i^k)\times p_i^k\) 倒序枚举即可,初始状态 \(f(0)=1\),最后答案是 \(\sum f(i)\)。
雾。。
\(n \times m\) 的网格图,某些各自上有一些障碍,求从 (1, 1) 到 (n, m) 有多少对不相交的且不经过障碍的两条路径。
solution
容斥 dp
不考虑相交的话,方案数就是一个最朴素不过的dp
\(f_{i,j} = 0~(a_{i,j} = 0)\)
\(f_{i, j} = f_{i,j} + f_{i - 1, j - 1} + f_{i,j -1}~(a_{i, j} = 1)\)
求相交的情况。(见题解吧,弄图有点麻烦)
矩阵优化
建议更换题面,哪来的鳄鱼?
solution
先不考虑鳄鱼的情况,求起点到终点走 \(k\) 步的方案数。
设 \(f_{i,j}\) 表示走了 \(i\) 步,到达 \(j\) 的方案数。
转移: \(f_{i,j} = \sum f_{i - 1, k}\times A_{k, j}\) 其中 \(A\) 表示 \(k\) 能否到达 \(j\)
题目中要求的走的步数很大,所以上面的式子可以用矩阵加速。
有鳄鱼?
发现 \(2\leq T \leq 4\) ,所以所有鳄鱼运动的总周期为12。可以计算 12 种变化矩阵 (有鳄鱼的标为 0),表示每个周期可以走的边,然后乘起来,然后求乘积的 \(\lfloor \frac{K}{12} \rfloor\) 次幂,乘以前的 \((K~mod~12)\) 种变化的乘积,就是总变化矩阵。用原矩阵乘以该矩阵就是答案,
复杂度:\(O(N^3 log K)\)