popossible の DP 选讲

popossible の DP 选讲

P4719 【模板】动态 DP

唯一听完基本能写出代码的题。

首先这个东西要是没有修改,那么就是那个没有上司的舞会。

$ f[i][0/1] $ : 第 $ i $ 个节点选/不选该节点的最大权值,然后从下到上 DP 即可。

然后发现改点权,会修改的 $ f $ 数组的值就是这个点到根节点上面的链的那一串 $ f $。

由于树有可能是链,所以要拿个什么玩意来平衡一下。

考虑树链剖分,$ f $ 数组的定义不变,再定义一个新的数组 $ g $,表示第 $ i $ 个节点选/不选该节点的最大权值(不包括重儿子)。

然后想一下如何由 $ f[v][0/1] $ 推到 $ f[u][0/1] $。

可以列出如下方程式:

\[f[u][0] = max(f[v][0]+g[u][0],f[v][1]+g[u][0]) \]

\[f[u][1] = max(f[v][0]+g[u][1],-INF) \]

发现这个东西有结合律,然后拿矩阵快速维护这个东西。

写出矩阵转移式:

\[\begin{bmatrix} f[v][0] & f[v][1] \end{bmatrix} * \begin{bmatrix} g[u][0] & g[u][1] \\ g[u][0] & -INF \end{bmatrix} = \begin{bmatrix} f[u][0] & f[u][1] \end{bmatrix} \]

重载乘法运算符,丢到线段树上维护。

然后修改的时候,因为 $ g $ 数组没有维护重儿子,所以每一次只会修改树剖上面的轻边,也就是最多只会修改 $ log_2 n $ 次。

每一次修改 $ log n $ ,所以总时间复杂度 $ n log^2 n $ 。

感觉这个只修改链头的 trick 跟 P5314 比较像。

P3350 [ZJOI2016] 旅行者

分治题。

首先对于每一次询问 Dijkstra 一遍肯定是不行的。

考虑离线下来。

每一次将这一小矩形按长边对半分割,得到两种询问。

第一种就是询问的两个点都在分割线的同一侧,第二种就是询问的两个点跨越的分割线。

对于第一种询问我们继续分割小矩形,然后处理第二种询问。

我们暴力枚举分割线上的每一个点,求出它们到这个矩形内每一个点的最短路径。

然后枚举被划在这个矩形内的每组询问,更新答案即可。

正确性显然,复杂度不会证。

P6192 【模板】最小斯坦纳树

状压 DP。

题目给你 $ k $ 个关键点,然后让你用最小的边权将它们联通,求这个最小边权。

首先设 $ f[i][S] $ : 子集为 S,并且一定包含点 i 的边权和最小值。

这个有两种转移。

第一种就是两个连通块之间的转移。

即 $ f[i][S] = min(f[i][S],f[i][T]+f[i][S \oplus T]) $ ($ \oplus $ 表示异或运算)。

第二种就是点与点之间的转移。

直接跑 Dijkstra 算法即可。

P1758 [NOI2009] 管道取珠

非常有趣的一个 trick 。

你发现这个贡献的平方非常奇怪,于是你考虑把它给拆开。

然后这个东西就相当于有两个人,第一个人的操作数目为 $ a_i $,第二个人的操作数目为 $ a_i $,将两个人的贡献乘起来。

然后就可以 DP 了。

令 $ f[k][i][j] $ 表示第一个人上面取了 $ i $ 个,第二个人上面取了 $ j $ 个的方案数。

列出转移方程式:

\[if \ (s[i]==s[j]) \ f[u][i][j]+=f[v][i-1][j-1]; \]

\[if \ (s[i]==t[k-j]) \ f[u][i][j]+=f[v][i-1][j]; \]

\[if \ (t[k-i]==s[j]) \ f[u][i][j]+=f[v][i][j-1]; \]

\[if \ (t[k-i]==t[k-j]) \ f[u][i][j]+=f[v][i][j]; \]

做完了。

P5024 [NOIP 2018 提高组] 保卫王国

动态DP多倍经验。

动态DP模板题要求的是最大权独立集,这题要求的是最小权覆盖集。

有个结论就是最小权覆盖集=全集-最大权独立集。

然后就是一样的了。

必须选和不能选可以转化为改点权。

必须选就改成负无穷大,不能选就改成无穷大,统计答案的时候就把加了的减掉就好了。

P4294 [WC2008] 游览计划

最小斯坦纳树多倍经验。

给你 $ k $ 个点,让你求把这 $ k $ 个点之间联通的最短路,然后输出方案。

输出方案不太好搞。

在每一次 Dijkstra 转移和枚举连通块的时候记录前驱,然后 DFS 一遍搞定。

还要注意点权化边权的一点细节,总得来说不是很难写,还是有点细节的。

P9871 [NOIP2023] 天天爱打卡

线段树优化 DP。

慢慢一步一步推就不是很困难。

首先想一个最简单的暴力DP。

设 $ f[i] $ 表示考虑到第 $ i $ 天可达到的最高能量值。

列出转移式子:$ f[i] = f[j-2] + w(j,i) + (i-j+1) * d $ ,其中 $ i-k+1 \le j \le i $。

其中 $ w(i,j) $ 表示的是 $ [i,j] $ 之间一直跑步可以造成的贡献数。

这个 $ w(i,j) $ 暴力算是 $ O(n) $ 的。

如果一个任务可以造成贡献那么 $ j<=l $ 且 $ r<=i $ 。

我们用一个 $ vector $ 将每个询问的在右端点处存下来,然后转移的时候从小往大加入。

那么 $ r<=i $ 这个条件是一定满足的,只需要考虑 $ j<=l $ 即可。

这个东西拿树状数组就可以维护。

然后这个 DP 就是 $ O(n k \log n) $ 的了。

你又发现其实不需要记录 $ n $ 种状态,因为真正有用的状态只有 $ m $ 个任务的两个端点。

离散化一下即可做到 $ O(m k \log m) $ 。

你又发现每一次转移过来的是一个区间的最大值。

然后每一次转移完的答案都会对后面的一个区间产生影响。

区间加区间最大值?

线段树即可。

posted @ 2025-03-29 14:52  LittleFoxFairy  阅读(48)  评论(0)    收藏  举报