知识学报:DP(1)

不是题解不是教学!!!

10.16

ATcoder Educational DP Contest A

题目给了 \(n \leq 1e5\) 个点,每个点有一个权值。从第 \(i\) 个点可以跳到第 \(i + 1\) 或第 \(i + 2\) 个点,花费是两点权值之差。
那么每个点都转移到后面两个点,并取被转移点的最小值就行了,不再赘述。

10.20

ATcoder Educational DP Contest B

和上题类似,但是跳的点从只有 \(i + 1\)\(i + 2\) 变成了可以一直跳到 \(i + k\)\(k \leq 100\)\(n \times k\) 足够小,暴力转移即可

ATcoder Educational DP Contest C

\(n \leq 1e5\) 天,第 \(i\) 天可以从三种活动中选一个做,分别加 \(a_i\)\(b_i\)\(c_i\) 的幸福值,但第 \(i\) 天和第 \(i - 1\) 天的活动不能一样,问最大幸福值。
对于第 \(i\) 天,我们只关心第 \(i - 1\) 天做了什么活动以及做这个活动前面的最大幸福值,所以只存第 \(i\) 天的做第 \(j\) 种活动的最大幸福值,第 \(i + 1\) 天的做第 \(j\) 种活动的最大幸福值就是第 \(i\) 天的另外两种活动的最大幸福值加 \(j_{i + 1}\),最后输出第 \(n\) 天做任意活动的最大值即可。

ATcoder Educational DP Contest D

\(n \leq 100\) 个物品和一个最大容量为 \(W \leq 1e5\) 的背包,每个物品有重量 \(w_i \leq W\) 和价值 \(v_i \leq 1e9\),问最大能装价值多少的物品。
对于第 \(i\) 个物品,我们只关心前面所有情况可能构成的价值和重量对,而有效重量最大为 \(1e5\),所以可以维护对于前 \(i\) 个物品选择了重量 \(used\) 的情况的最高价值。转移的方式是从所有可以选择第 \(i\) 个物品的 \(used\) 选择该物品后,\(used + w_i\) 记录的最高价值和这个价值比较并更新为较大值。也就是 \(dp[used + w_i] = \max(dp[used + w_i], dp[used] + v_i)\)

ATcoder Educational DP Contest E

和上题类似,但 \(W \leq 1e9\)\(v_i \leq 1e3\)
我们关心的东西不变,但有效重量过大无法维护,考虑维护价值,记录选了前 \(i\) 个物品时总价值为 \(x\) 时最少的重量。对于第 \(i\) 个物品,选择一个可以选择的价值 \(x\),然后更新 \(x + v_i\),就是 \(dp[x + v_i] = \min(dp[x + v_i], dp[x] + w_i)\)。最后输出最大的选择重量小于 \(W\) 的价值。
注意要把 \(dp\) 数组都初始化为一个足够大的数。

ATcoder Educational DP Contest F

题目给两个长度为 \(n\)\(m\) 的字符串,问这两个串的最长公共子序列是什么,\(1 \leq n,m \leq 3000\)
先考虑最长公共子序列的长度,容易想到状态设计为 \(s1\) 中已经用到第 \(i\) 个,\(s2\) 中用到第 \(j\) 个时最长公共子序列为多少。状态转移就是如过 \(s1[i] = s2[j]\),那么 \(dp[i][j] = \max (dp[i - 1][j - 1] + 1, dp[i][j])\),否则 \(dp[i][j] = \max (dp[i - 1][j], dp[i][j - 1])\)
接下来考虑怎么得到具体的字符串,首先我们不能每次转移时带着整个字符串,否则时间复杂度会达到 \(O(n^3)\)。除了用指针之外,我们发现我们可以通过最终状态回溯到一开始的状态,我们可以设置一个 \(x\)\(y\) 初始分别为 \(n\)\(m\),当 \(s1[x] = s2[y]\) 时,证明这里从 \((x - 1, y - 1)\) 转移过来,否则则是从 \((x - 1, y)\)\((x, y - 1)\) 中较大的那个转移过来。我们只需要在第一种情况时把当前字符加入 \(ans\),最后再反转输出即可。
需要注意的是第一种情况的判断只能是判断字符,而不能通过 \(dp[x][y] = dp[x - 1][y - 1] + 1\) 判断,因为有可能恰好相等,但实际上不可以转移。

ATcoder Educational DP Contest G

题目给了一个有 \(n \leq 1e5\) 个节点 \(m \leq 1e5\) 条边的有向图,问最长的一条路径有多长。
以一个点为起点跑一次 dfs 就可以求出以他为起点的最长路径,但这样复杂度会达到 \(O(n^2)\),所以要记忆化。每个点跑过一次之后记录下来以这个点为起点的最长路径,再用到就直接返回即可。

ATcoder Educational DP Contest H

题目给了一个 \(n \times m, 1 \leq n,m \leq 1000\) 的矩阵,其中有一些格子不能走,只能往右或者往下走,问走到右下角的路线有多少种。
走到每个节点的走法就是他上面节点的走法加左面节点的走法。如果这个节点不能走,那么走到这个格子的方法数为 \(0\)

10.21

ATcoder Educational DP Contest I

题目给了 \(n \leq 2999\) 个奇数个硬币,每个硬币抛出正面的概率为 \(p_i\),反面为 \(1-p_i\),问最后正面的硬币比反面多的概率。
把状态设计为投了前 \(i\) 个硬币中有 \(j\) 个是正面的概率,那么初始情况就是投了 \(0\) 个出现 \(0\) 个正面的概率为 \(1\)。转移就是 \(dp[i][j] = dp[i - 1][j - 1] * p[i] + dp[i - 1][j] * (1 - p[i])\)
最后统计出 \(j \geq \lceil\frac{n}{2}\rceil\) 的所有 \(dp[i][j]\) 的和就是最终答案。

10.22

ATcoder Educational DP Contest J

给了 \(n \leq 300\) 个盘子,每个盘子中装有 \(1,2\)\(3\) 个寿司。每次随机选择一个盘子,如果这个盘子里有寿司就吃一个,否则就什么也不做。问吃光所有寿司的期望轮次。
容易发现状态可以设计为 \(dp[i][j][k]\),表示剩 \(1,2,3\) 个寿司的盘子有 \(i,j,k\) 个时,从初始状态吃到这个状态的期望。我一开始想的比较单纯,分别计算了当前吃 \(1,2,3\) 个盘子的期望次数,但会出现一个问题,就是如果吃某个含有相应数量的寿司之前,吃了另一个有不同数量寿司的盘子,那么转移就不是我们希望的只吃一个要求盘子的情况。
此时叶神下课过来,他给了一种想法,去计算吃掉一个寿司的期望,然后吃掉每一种盘子的期望都加上这个。这种做法因为吃掉每种盘子的概率不同,所以挂掉了。
然后我试着改变状态的设计为从当前状态吃到全零状态的期望,这样就可以得到下面的式子:

\[dp_{i,j,k} = (dp_{i,j,k} + 1) \times p_0 + (dp_{i-1,j,k} + 1) \times p_1 + (dp_{i+1,j-1,k} + 1) \times p_2 + (dp_{i,j+1,k-1} + 1) \times p_3 \]

推导一下可以得到:

\[dp_{i,j,k} = \frac{dp_{i-1,j,k} \times p_1 + dp_{i+1,j-1,k} \times p_2 + dp_{i-1,j+1,k-1} \times p_3}{1-p_0} \]

这样就可以正确的计算每个传进来的期望按概率分配了。用到的是总期望等于每个部分期望乘这个期望的概率。需要注意的是从最终状态往前去推导一般更容易得到递推式。

10.29

ATcoder Educational DP Contest K

给定有 \(n \leq 100\) 个数的数组 \(a\),以及有 \(k\) 个石子。两人轮流拿走恰好 \(a\) 中某个值的石子,最后谁无法拿就输了,问谁获胜。
可以没有石子的状态往前推到 \(k\) 个石子的状态,对于一个石子的状态,如果可以取一些石子后到达一个必败态,那么这个状态必胜,否则必败。推到 \(k\) 个石子的状态时,恰好该先手行动,此时直接根据这个状态必胜还是必败即可。

11.2

ATcoder Educational DP Contest L

\(n \leq 3000\) 个数,两人轮流选择两端的某个数删掉,删掉的数是自己的得分。每个人都希望自己的得分尽量多,问最后先选减后选的人的得分是多少。
从反向考虑,把删除变成增加,那么每个区间 \(l\)\(r\) 的答案就是 \(\max(-dp[l][r-1] + a[r], -dp[l + 1][r] + a[l])\),利用区间 dp 可以在 \(O(n ^ 2)\) 解决。一个人想要多选,那么一定希望另一个人尽量少选,所以把上一层 dp 的结果倒过来再取最大值就行。

ATcoder Educational DP Contest M

\(n \leq 100\) 个人,\(k \leq 1e5\) 颗糖,每人最多可以分到 \(a_i\) 颗糖,要求所有糖全部分完,问方案数。
容易想到对于每一个人,遍历总剩余糖数的方案数,然后转移分给他 \(0\)\(a_i\) 颗糖的情况。然而这样的复杂度达到了 \(O(n \times k ^ 2)\),无法接受。发现对于每个剩余糖的情况,都是转移到前面的一个区间,于是用一个差分去维护,就可以把一次计算的复杂度降低到 \(O(k)\) 了。

ATcoder Educational DP Contest N

\(n \leq 400\) 只史莱姆,每次可以合并相邻的两个,花费为两只史莱姆大小的和,问最少花费多少合并所有史莱姆。
典型的区间 dp。设 \(dp_{i,j}\) 为把 \(i\)\(j\) 合并到一起的花费,那么去遍历 \(i\)\(j\) 中的所有数,对于遍历到的数 \(k\),把整个区间分成 \(i\)\(k\)\(k + 1\)\(j\) 两个区间,花费就是 \(dp_{i,k} + dp_{k + 1, j} + 这段史莱姆大小\)。史莱姆大小之和可以用前缀和简单维护,这样就可以 \(O(n^3)\) 解决。

ATcoder Educational DP Contest O

\(n \leq 21\) 个男性和女性,其中每个男性和女性之间有的可以配对,有的不能配对。要求把所有男女两两配对且最后所有人都有配对的方案数。
把女性是否已经配对状压,对于每个男性,遍历所有情况,如果某种情况下某个可以与这个男性配对的女性仍未配对,那么把这个女性配对并转移,最后输出所有女性都配对的方案数。这样就可以做到 \(O(n ^ 2 \times 2 ^ n)\) 的复杂度,其中应该还可以再优化一些,但目前这种做法足以通过。
如果采用遍历所有状态,对于每个状态,计算出其中已经被选择的女性的数量,那么就可以直接得到当前要匹配第几个男性,这样就可以省掉遍历男性的一维,达到 \(O(n \times 2^n)\) 的复杂度

11.3

ATcoder Educational DP Contest P

给定一颗 \(n \leq 2e5\) 个顶点的树,要求为每个节点涂上黑色或白色,不能有两个相邻节点均为黑,问涂色方案数。
树上 dp,对于每个节点分别设置以该节点为白色或黑色根的子树涂色方案数,转移时黑色根是所有子节点白色相乘,白色根是所有子节点黑色加白色方案数相乘。

ATcoder Educational DP Contest Q

给定 \(n \leq 2e5\) 朵花,每朵花有一个魅力值 \(a \leq 1e9\) 和高度 \(h \leq 2e5\),要求从左到右选择一些花,使高度递增,问最大可能魅力值。
由于 \(a\) 非常大,不能在值域上 dp,那么考虑利用高度 dp。容易想到 \(O(n \times h)\) 的做法,即从左往右遍历每朵花,对于一朵高度为 \(h_i\) 的花找到小于 \(h_i\) 的所有高度的魅力值,并加上这朵花的魅力值再更新高度为 \(h_i\) 的魅力值。可以发现遍历小于 \(h_i\) 的魅力值是区间查找最小值,利用线段树可以把单次查询复杂度降至 \(O(logh)\)

ATcoder Educational DP Contest R

给定一个 \(n \leq 50\) 的有向图,问长度恰为 \(k \leq 1e18\) 的路径有多少条。
考虑矩阵快速幂优化,定义乘法为 \(a\) 矩阵和 \(b\) 矩阵相乘的结果为对于从 \(i\)\(j\) 走的方案数为 \(\sum_{k = 1}^{n}a[i][k] \times b[k][j]\),单位矩阵为每个 \(i = j\)\(1\)

11.4

ATcoder Educational DP Contest S

给出小于 \(n \leq 10^{10000}\) 的数中是 \(d \leq 100\) 的倍数的数的数量。
数位 dp。\(dp[i][j][k]\) 维护第 \(i\) 位选 \(j\) 时对 \(d\) 取模结果为 \(k\) 的情况。

11.5

ATcoder Educational DP Contest T

给定一个长度为 \(n-1,n \leq 3000\) 的字符串,仅包含 \(<\)\(>\)。要求构造一个长度为 \(n\) 的排列,使 \(i\)\(i + 1\) 的关系满足第 \(i\) 个符号。
设置状态 \(dp[i][j]\) 为构造一个满足前 \(i - 1\) 个条件的长度为 \(i\) 排列的以 \(j\) 为结尾的情况数,转移时可以视为在末尾插入新值 \(k\) 并把所有大于新值的原排列中的数加一,最后就是题目要求的结果。利用前缀和,可以达到 \(O(n)\) 的单次转移。

ATcoder Educational DP Contest U

给了 \(n \leq 16\) 只兔子,每两只兔子间有一个值,把所有兔子任意分组,在同一组内的兔子会把他们之间的值累加到答案上,问答案最大为多少。
考虑状压 dp,一个新的状态等于两个他的互补子集答案和,第一个子集答案为新加同一组中的兔子间所有值的和,第二个子集为这些兔子已经分好组的最大答案。
第一个子集的答案可以通过预处理,一个状压 dp 得到。
遍历一种情况的所有子集作为第一个子集,总体复杂度为 \(O(3^n)\)。要求 \(T\) 为集合 \(S\) 的全部子集,可以利用 \(T = (T-1) \& S\) 得到,直到 \(T = 0\) 为止。
遍历子集的子集,除了求分组最大或最小以外,也可以用于要求满足一些条件后分最少组。对于第一个子集部分,可以视作选的东西满足条件为 \(1\),不满足则为无穷大。常见变体是图论中求最小团个数,团是一个团内所有点都两两相连。

ATcoder Educational DP Contest V

给定一颗有 \(n \leq 1e5\) 个点的树,要求为每个点涂上黑色或白色,要求所有黑色点间相互仅通过黑色点连通,问任意某个点涂黑时的方案数。对一个数 \(1 \leq m \leq 1e9\) 取模。
抛开取模,很容易想到一个换根 dp。从上到下 dp 一次求出该点为黑时子树有多少种涂法,父节点涂法数为所有子节点涂法数加一再相乘。然后从上到下,更新该点为黑时涂法为父节点涂法总数除以自己贡献的部分。然而对任意数取模不能使用除法。
第一次 dp 时,对每个点保存他孩子的前缀积数组和后缀积数组,这样就可以在第二次 dp 时直接获得除了某个孩子以外其他孩子的贡献的积,然后再利用类似标记永久化的方法可以统计第二次 dp 时从上到下的贡献。

11.6

ATcoder Educational DP Contest W

给定一个长度 \(n \leq 2e5\),在其中有 \(m \leq 2e5\) 个区间,每个区间有 \(l \leq r\) 和一个数 \(-1e9 \leq a \leq 1e9\),对于一个长度为 \(n\)\(01\) 串,如果区间 \(i\) 内有至少一个 \(1\),那么最终答案增加 \(a_i\)。问最大答案为多少。
考虑设计 \(dp[i][0]\) 为第 \(i\) 个位置填 \(0\)\(dp[i][1]\) 为第 \(i\) 个位置填 \(1\)。考虑转移,容易看出的是 \(dp[i][0] = \max(dp[i-1][0], dp[i-1][1])\),考虑 \(dp[i][1]\) 的转移。
暂时只考虑第 \(i\) 位会影响到的区间,由于填了这个 \(1\) 后,有些区间已经被选过了,所以要把区间按 \(l\) 排序,对于 \(l \leq i \leq r\) 的每个区间,考虑第 \(j\) 个区间的及后面都没有出现 \(1\),那么增加的答案是后面这些区间。扩展一些,可以写作 \(dp[i][1] = \max_j(dp[j][1] + j后面的区间)\)。可以发现,每个区间都会给所有小于 \(l\)\(j\) 做贡献,故而可以使用线段树。当 \(i\) 大于 \(k\) 区间的右端点时,再在线段树里区间减小前 \(l_k\) 的贡献即可。

ATcoder Educational DP Contest X

\(n /leq 1e3\) 个积木,每个积木有一个重量 \(w_i \leq 1e4\) 和一个承重力 \(s_i \leq 1e4\),以及一个价值 \(v_i \leq 1e9\)。选择一些积木垒成一摞,要求一个积木上面所有积木重量小于等于它的承重力,求最大价值和。
在遥远的记忆中,帅帅还是国正讲过一道题,就是把这个题目的 \(v\) 全部变成一,我知道结论是一个贪心。于是把所有积木先排成一排,假设 \(i\)\(j\) 下面且不可交换。那么可以得到:

\[W \leq s_i \]

\[W + w_i \leq s_j \]

\[W + w_j > s_i \]

计算可得 \(w_i + s_i < w_j + s_j\)
按照这个排序后,01 背包解决即可。

ATcoder Educational DP Contest Y

给定一个 \(n \times m,n \leq 1e5, m \leq 1e5\) 的矩阵,其中有 \(k \leq 3000\) 个不可经过的点。每步只能往右或下走,问走到右下角方案数。
显然这道题几乎不可能对 \(n\)\(m\) 下手,于是考虑 \(k\)。首先可以通过组合数学,及向右向下共走 \(n+m-2\) 步,向右走 \(n-1\) 步,得出没有不可经过点的方案数。考虑容斥原理,假设只包括一个不可经过点,那么不可经过点到右下角的路径数乘左上角到不可经过点的路径数就是无效方案数,减去即可。对于更多不可经过点同理,只要将不可经过点排序后,处理出到每个不可经过点的有效方案数,当一个不可经过点包括另一个时,方案数就像刚刚那样减去即可,不可经过点两两间的路径也必不包含。

ATcoder Educational DP Contest Z

\(n \leq 2e5\) 块石头和一个常数 \(c\),每个石头有一个高度 \(h_i\),保证 \(h_i \leq h_{i+1}\)。青蛙从第一块石头出发,每次跳跃可以跳到任意石头,花费为 \((h_j - h_i) ^ 2 + c\)。求跳到终点的最小花费。
容易想到 \(O(n^2)\) 的做法。从后往前 dp,\(dp[i] = \min_j(dp[j] + c + (h_j - h_i)^2)\)
把转移方程拆开可得:

\[dp[i] = c + h_i^2 + \min_j(dp[j] + h_j^2 -2 \times h_j \times h_i) \]

可以注意到,我们要求的最小值部分中是很多以 \(-2h_j\) 为斜率,\(dp[j]+h_j^2\) 为截距的直线,要求的值就是自变量为 \(h_i\) 时的函数值。可以发现斜率随 \(j\) 的减小而减小,\(h_i\) 也是不断减小,可以感受到如果一个 \(j\) 较大的直线大于等于一个 \(j\) 较小的直线在 \(h_i\) 上的函数,那么这个较大的 \(j\) 的直线就可以丢掉。同时,如果一个 \(j\) 被他的前一条和后一条直线形成的最小值的连线包裹住了,那么这个 \(j\) 也可以丢掉。
于是使用双端队列维护,把每个新增的直线塞在队列尾,也就是队列前面比后面的 \(j\) 大。在计算一个 \(\min\) 的值前,如果队列头大于等于第二个直线的函数值,那么就丢掉,直到不能丢为止,此时队列头就是最小的函数值。在往队列尾加入新的 \(i\) 直线前,将队列尾和新直线一起去求与队列倒数第二个相交的点,如果大于等于新直线相交的横坐标,那么丢掉队列尾。计算时,为了精确性,可以选择不使用除法,如下,设 \(p > q > r\)

\[\frac{q_c - p_c}{q_k - p_k} \geq \frac{r_c - p_c}{r_k - p_k} \]

把分母乘过去即可。注意可能会溢出 long long。

完结撒花!

posted @ 2025-10-16 23:14  vivid_stareium  阅读(8)  评论(0)    收藏  举报