DP技巧与DP杂题
DP常用技巧
- 增加维数
- 交换答案与状态
- 可行解转最优解
- 删掉本质相同的状态
- 对部分状态\(dp\)
- 遇到转移顺序的困难,考虑记忆化搜索
- 遇到转移细节过多的问题,考虑从 \(i\rightarrow i+1\) 而不是 \(i-1\rightarrow i\)
- 考虑状态时,先把需要记下来的都记一遍,再考虑优化
DP杂题
CF837D (可行性转最优化)
题目
我们把一个数的 roundness 值定义为它末尾 \(0\) 的个数。
给你一个长度为 \(n\) 的数列,要求你从中选出 \(k\) 个数,使得这些选出的
数的积的 roundness 值最大。
\(n\ge 200\)
题解
考虑一个很暴力状态,设\(f[i][j][a][b]\)表示考虑到第\(i\)个数,选了\(j\)个,因子\(2\)和\(5\)的个数分别是\(a,b\)的方案是否存在,答案就是对\(f[i][k][a][b]=true\)的状态取\(\min(a,b)\)
我们发现直接这样转移的复杂度是\(\mathcal O(n^4)\)的,无法通过本题。
所以我们考虑套路可行解转最优解,设\(f[i][j][a]\)表示考虑到第\(i\)个数,选了\(j\)个,因子\(2\)个数为\(a\)时因子\(5\)最多是多少
这样子复杂度就降为了\(\mathcal O(n^3)\),可以通过本题
CF677D (分层图dp)
题目
有一个 \(n\times mn×m\) 的矩阵 \(a(1\le a_{i,j}\le p)\) ,求从起点 \((1,1)\) 出发依次遍历值为 \(1\to p\) 的矩阵单元的最短路径曼哈顿距离。保证满足 \(a_{i,j}=p\) 的 \((i,j)\) 唯一。
数据范围:\(1\le n,m\le 300,1\le p\le n\cdot m\)
题解
我们先把数按照种类分组,分成 \(p\) 组。
\(f[i][j]\) 表示到达目前这个数的最短路,那么转移方程为 \(f[i][j] = \min\lbrace f[x][y]+|x-i|+|y-j|\rbrace\),其中 \((x,y)\) 为上一组中所有数的坐标。
然后要是直接暴力的状态转移的话,是要TLE的,考虑进行优化(下面这个优化真的太神仙了)。
考虑一个界限 \(K\),假设当前组为 \(\text{T}[i]\),上一组为 \(\text{T}[i-1]\),
那么当 \(\text{T}[i].\text{size} \le K\) 时,我们就用继续用上面的暴力动态转移,那么对于所有的“上一组”点数加起来不会差过 \(nm\),因此总时间复杂度 \(\mathcal O(K \cdot nm)\);
如果 \(\text{T}[i].\text{size} > K\),我们在网格上进行多源点的优先队列BFS,源点是所有的 \(\text{T}[i-1]\) 组内的点,搜出到所有 \(\text{T}[i]\) 组内的点的最短距离,这样BFS最多跑一遍所有网格,时间复杂度 \(\mathcal O(nm \log(nm))\);由于这样的组数目不会超过 \(\frac{nm}{K}\) 个,所以总时间复杂度为 \(\mathcal O(\frac{nm}{K} nm \log(nm) )\)。
这样一来,两种加起来的总时间复杂度就是 \(\mathcal O(Knm+\frac{nm}{K}nm\log(nm) ) = \mathcal O( nm (K + \frac{nm\log(nm)}{K}) )\),由此可知取 \(K=\sqrt{nm \log(nm) }\) 时,时间复杂度最小,为 \(\mathcal O(nm\sqrt{nm\log(nm)})\)
P1004 [NOIP2000 提高组] 方格取数(去除冗余状态)
题意
给定一个 \(n\times m (n,m\le 200)\) 的矩阵,每个格子里有不同的正整数。一个人从左上角走到右下角,走两次,取走两次路径上的数(同一个数不可以重复取),最大化这些数的和
题解
显然我们可以设 \(f[i][j][k][l]\) 表示第一次走到 \((i,j)\) ,第二次走到 \((k,l)\) 时的最大值
考虑如何优化
首先,两次走的总步数一定是 \(n+m\) 的,同理,如果我们控制两次同时走的话,那么有 \(i+j=k+l\) ,这样我们就可以设 \(s=i+j\) 省下一维,转移方程很显然,注意两次相遇的问题即可
BZOJ 1801 中国象棋(DP) (去除不必要的维度)
题目
在\(n\times m\)的棋盘上放若干炮使得不互相攻击。有多少种放法?
说人话就是,不存在三炮共线的情况有多少种
\(n,m\ge 100\)
题解
首先,一个显然的想法是进行状压\(dp\),设\(f[i][s]\)表示考虑到第\(i\)行,\(s\)是一个三进制状态,存每一列都用了多少个炮,这样转移的复杂度是\(3\)的指数级的,显然无法通过
我们考虑一件事,就是假设前\(i\)行的炮都放完了,那么第\(i+1\)行的炮能放的方案数只与剩余了多少位置能放有关,我们并不需要知道这些炮具体放在了什么位置
所以我们可以设\(f[i][j][k]\)表示,考虑完前\(i\)行,有\(j\)列还可以放\(1\)个炮,\(k\)列还可以放\(2\)个炮的总方案数
我们考虑转移
- 放0个炮,那么就是:
- 放1个,可以放在还可以放1个炮的列中,也可以放在还可以放2个炮的列中:
- 放2个,可以都放在还可以放1个炮的列中,也可以都放在还可以放2个炮的列中,也可以每边放一个:
[SCOI2008]着色方案 (利用题目性质优化状态)
题目
有 \(n\) 个木块排成一行,从左到右依次编号为 \(1\) 至 \(n\)。
你有 \(k\) 种颜色的油漆,第 \(i\) 种颜色的油漆足够涂 \(c_i\) 个木块。
且保证 \(\sum_{i=1}^kc_i=n\)。
统计任意两个相邻木块颜色不同的着色方案
题解
一个变态朴素的想法,我们进行\(15+2\)维的\(dp\),那\(15\)维状态存对应的颜色剩余的个数,但是显然\(5^{15}\)实在无法令人接受
我们意识到一个问题,使相邻木块颜色不同并不需要枚举每一种颜色,事实上,我们只要保证当前颜色不与上一次的颜色相同,那么就是合法的转移,所以我们考虑优化状态
设\(f[i][j][a][b][c][d][e]\)表示考虑到第\(i\)位,上一次选的数还剩\(j\)次可选,剩余\(1,2,3,4,5\)次的数分别还有\(a,b,c,d,e\)个,我们上一次选的这时候我们的状态数就为\(15^{5}\),这比上面那个东西小多了,复杂度是正确的
我们不能选的状态,就是剩余次数为\(j\)的颜色其中一个,直接转移就行
[AGC044A] Pay to Win (记忆化搜索,大力出奇迹)
题目
你需要通过如下操作把 \(0\) 变成 \(N\) \((N \le 10^{18})\)
- 花 \(\text{A}\) 个金币把数字乘 \(2\) 。
- 花 \(\text{B}\) 个金币把数字乘 \(3\) 。
- 花 \(\text{C}\) 个金币把数字乘 \(5\) 。
- 花 \(\text{D}\) 个金币把数字加一或减一。
求最少需要花费的金币。
题解
倒着记忆化搜索,乘法变成除法,遇到除不尽的通过加法调整
这样形成的就是一个深度为 \(\log N\) 的六叉树
时间复杂度 \(\mathcal O(\text{能过})\)
[USACO2.3]奶牛家谱 Cow Pedigrees (恰好到不超过)
题目
一个有 \(n\) 个节点,深度为 \(k\) 的无标号完满二叉树(即每个节点的儿子数为 \(0\) 或 \(2\))有多少种结构?定义根节点深度为 \(1\)。
答案对 \(9901\) 取模。
\(3\le n < 200\),\(2 \le k < 100\)。
题解
考虑到 \(\text{恰好k个}\) 并不好做,所以我们考虑 \(\text{不超过k个}\) 怎么做
设 \(f[i][j]\) 表示点数为 \(i\) ,最大深度不超过 \(j\) 的二叉树的个数
枚举其左儿子大小进行转移即可
复杂度 \(\mathcal O(n^2k)\)
ZR2022 NOIP十连测Day6 T1 跳棋(有向图+tarjan缩点+拓扑排序/记忆化搜索的一类经典套路)
sb出题人卡常数,下面这个做法过不了
题目
在一个无穷大的跳棋棋盘上,放有 \(n\) 个棋子,第 \(i\) 个棋子的坐标为 \((x_i,y_i)\) ,问当前局面下各个棋子分别能够到达多少个位置
题解
考虑将每个棋子的答案分成两部分
-
周围六格中没有放棋子的部分
拿 \(\text{map}\) 记一下各个棋子的位置,然后枚举就行, \(\mathcal O(n)\)
-
连续跳跃的部分
首先连续跳能够跳到的格子一定不超过 \(6n\) 个,因此能跳的位置连上有向边,边数也是 \(\mathcal O(n)\) 级别的
然后用 \(\text{tarjan}\) 给有向图缩点,跑拓扑排序或者记忆化搜索得到答案
P7516 [省选联考 2021 A/B 卷] 图函数 (Floyd最短路dp)
PS:因为最短路算法和dp有着千丝万缕的联系,所以我在这里放几道偏图论的题应该问题不大(逃
题解
第一步,拆贡献,我们直接计算每个点对 \(h(G)\) 的贡献
我们考虑一个问题,两个点可以相互到达,就说明了这两个点不在同一个强连通分量中,那么对于点 \(u\) ,我们考虑完前 \(x-1\) 个点之后,后面的图上的点都没有办法走到这 \(x-1\) 个点,因为前 \(x-1\) 个点已然不在强连通分量中,没有必要管了。
那么现在问题转化为,对于前 \(n\) 个图 \(G_1\sim G_n\) 分别求出有多少个点对 \((u,v),u\ge v\) 满足存在不经过 \(< v\) 的点使得两点可以互达
一个点对会对答案的前缀产生贡献,为了使答案最大化,我们贪心地考虑每个点对都通过一条使得其最小编号点最大的路径,差分一下处理即可
那么怎么求解两点间经过的最小编号点呢?仔细一想,这很符合 \(\text{Floyd}\) 算法的转移过程,所以使用 \(\text{Floyd}\) 求解即可
时间复杂度 \(\mathcal O(n^3+m)\) 勉强能冲
P3953 [NOIP2017 提高组] 逛公园 (分层图和spfa最短路dp)
题意
给你一张 \(n\) 个点 \(m\) 条边的有向图,起点为 \(1\) ,终点为 \(n\) ,边有非负边权(可以为 \(0\) )。求解使得 \(\text{dis}(1,n)\le \min\text{{dis}}(1,n)+K\) 的路径数
\(n\le 1\times 10^5,m\le 2\times 10^5,K\le 50\)
题解
神仙题!
由于 \(k\) 值很小,所以考虑建立分层图,设 \(f[i][j]\) 表示 \(\text{dis}(1,i)= \min\text{{dis}}(1,i)+j\) 的方案数,对于一条边 \((u,v,w)\) 转移为
其中 \(\text{dis}_u\) 代表的是从 \(1\) 到 \(u\) 的最短路
但是,我们发现这个转移在 \(0\) 环的情况下是错误的,若 \(0\) 环出现在最短路图上,则直接导致无解。于是考虑一件事,最短路图在无 \(0\) 环的情况下是个 \(\text{DAG}\) ,因此我们可以对最短路图进行拓扑排序判环,有环则当前层无解,否则拿记忆化搜索直接跑(不用考虑转移顺序多是一件美事),时间复杂度 \(\mathcal O(mk)\)
P7077 [CSP-S2020] 函数调用(DAG建模+dp)
题解
首先考虑一个性质,如果只存在 \(1\) 操作,那么直接做就行,所以考虑如何将 \(2,3\) 两个操作转化成 \(1\) 操作
关键点在于:
- 某个函数被调用后序列被乘k,等价于这个函数被执行k次
- 乘法可以使用乘法标记
然后拓扑排序dp处理处理即可
P7962 [NOIP2021] 方差 (差分套路+dp)
题目
给定长度为 \(n\) 的非严格递增正整数数列 \(1 \le a_1 \le a_2 \le \cdots \le a_n\)。每次可以进行的操作是:任意选择一个正整数 \(1 < i < n\),将 \(a_i\) 变为 \(a_{i - 1} + a_{i + 1} - a_i\)。求在若干次操作之后,该数列的方差最小值是多少。请输出最小值乘以 \(n^2\) 的结果
其中方差的定义为:数列中每个数与平均值的差的平方的平均值。更形式化地说,方差的定义为 \(D = \frac{1}{n} \sum_{i = 1}^{n} {(a_i - \bar a)}^2\),其中 \(\bar a = \frac{1}{n} \sum_{i = 1}^{n} a_i\)
\(1 \le n \le {10}^4\),\(1 \le a_i \le 600\)
题解
初看这个操作感觉很无厘头是吧?那么试着把它差分吧!
于是我们发现操作等价于交换原数列差分数组中的两个数
然后我们不喜欢那个丑到爆的方差定义,于是设 \(S=\sum_{i=1}^na_i\) ,转移就是
还有给定的 \(a_i\) 是单调递增的,所以差分序列是恒大于 \(0\) 的
考虑如何得到最优答案,感性理解一下,答案序列应该是个单谷序列,证明可以考虑调整法,这里不赘述
先将 \(a_i\) 按差分数组排序,然后设 \(f[i][s]\) 表示考虑了前 \(i\) 个数,\(\sum a_i=s\) 时最小的 \(\sum a_i^2\) 是多少,分类讨论当前数应该放在前面还是后面,于是有
时间复杂度 \(\mathcal O(n^2a)\)
P7961 [NOIP2021] 数列 (跟二进制进位有关的dp)
题面
给定整数 \(n, m, k\),和一个长度为 \(m + 1\) 的正整数数组 \(v_0, v_1, \ldots, v_m\)。
对于一个长度为 \(n\),下标从 \(1\) 开始且每个元素均不超过 \(m\) 的非负整数序列 \(\{a_i\}\),我们定义它的权值为 \(v_{a_1} \times v_{a_2} \times \cdots \times v_{a_n}\)。
当这样的序列 \(\{a_i\}\) 满足整数 \(S = 2^{a_1} + 2^{a_2} + \cdots + 2^{a_n}\) 的二进制表示中 \(1\) 的个数不超过 \(k\) 时,我们认为 \(\{a_i\}\) 是一个合法序列。
计算所有合法序列 \(\{a_i\}\) 的权值和
\(1 \leq k \leq n \leq 30\),\(0 \leq m \leq 100\),\(1 \leq v_i < 998244353\)。
题解
由于有进位的情况出现,因此考虑从低位向高位dp
设 \(f[i][j][k][l]\) 表示考虑到第 \(i\) 位,确定了 \(a\) 序列中的前 \(j\) 个元素,有 \(k\) 个 \(1\),到下一位有 \(p\) 的进位,考虑给 \(S\) 的第 \(i\) 位贡献了 \(p\) 位,那么转移就为
时间复杂度 \(\mathcal O(n^4m)\)
P6758 [BalticOI2013] Vim(线头dp)
原作者题解,这里引用一下LOJ2687 BalticOI2013 Vim 线头DP,因为原作者写的实在是太好了,所以这里建议去原作者博客获得更好的阅读体验,放这里是方便笔者自阅
题解
原题等价于:给出一个序列和两种移动方式,移动过程中必须要经过某一些点,求最小代价。
把连续的f操作和连续的h操作看成线,那么移动路线如下
首先,考虑下面两种移动路线
显然A路线一定没有B路线优
上面的条件等价于对于某一个位置 \(i\),经过的位置覆盖了位置 \(i\) 与 \(i+1\) 之间的线段的线的数量要么是 \(1\) ,要么是 \(3\) ,对应下图的 AB 两种情况。
设 \(p_{i,j}\) 表示覆盖了 \(i\) 与 \(i+1\) 之间的线段 \(1\) 次,且 \(f\) 操作选择字符 \(j\) 的最小代价,\(q_{i,j,k}\) 表示覆盖了 \(i\) 与 \(i+1\) 之间的线段 \(3\) 次,且在进行 \(h\) 操作之前的 \(f\) 操作选择的字符是 \(j\)、在进行 \(h\) 操作之后的 \(f\) 操作选择的字符是 \(k\) 的最小代价
又设 \(s_i\) 表示字符串的第 \(i\) 个字符,\(\text{imp}_i\) 表示原串中第 \(i\) 个字符前是否存在字符 \(e\)
转移:
\(p_{i,j}\) 的转移分别对应下图的ABCD情况
其中虚线表示新加入的线,红色字表示对应位置的字符类型,黑色字表示位置编号
\(q_{i,j,k}\) 转移分别对应下图中的ABCDEF情况
转移就是把线延长和补全的过程,所以叫做线头DP
代码源省选班 字符串 (子段切分问题)
题面
给定一个长度为 \(n\) 的由 AB?
组成的字符串,求有多少种方式可以把每个 ?
替换为 A
或 B
后,字符串中,长度为 \(m\) 的连续全 A
或全 B
字串恰好有 \(k\) 个
题解
一种朴素的状态是: 设 \(f[i][j][p][c]\) 表示考虑到第 \(i\) 个数,当前有 \(j\) 个合法子串,已经有 \(p\) 个连续的字符 \(c\) 时的方案数,转移是 \(\mathcal O(n^3)\)
考虑优化,去掉 \(p\) 一维,每次不再填一个字符而是填一个极长连续段,这是这种子段切分问题的一个常见优化思路
那么就有转移
其中 \(\text{len}\) 表示以 \(i\) 为端点不包含 \(c'\) 的子串长
二维前缀和优化即可
时间复杂度 \(\mathcal O(n^2)\)
ZR2020 提高组十连测 day5 T3 (点边数差异不大时的一种处理图上dp的做法)
题意
过了一阵子的辉夜又开始想起了这件事,她开始在意起如果那时候她真的出去乱走,到底什么时候能回到这里呢?可以把街区抽象为一个连通的无向简单图 \(G=(V,E)\),这个楼梯在 1 号点,每到一个点 \(i\) ,辉夜都要休息 \(w_i\) 分钟(包括在 1 号点出发之前),多次到达一个点的话需要多次休息。离开一个点的时候,迷茫的辉夜会等概率选择任意一条与这个点相连的边走过去(假定走路不需要时间)。现在辉夜得到了地图,她想知道从楼梯( 1 号点)出发,第一次回到这里时的期望花费的时间(注意这包含了出发前的休息时间,但回到 1 号点后的休息时间不计入),你能告诉她吗?
题解
\(n,m\le 500\) 是图上随机游走的板子,高斯消元即可,时间复杂度 \(\mathcal O(m^3)\)
树的情况就考虑树形dp,设 \(f_i\) 表示从 \(i\) 出发走向 \(i\) 的子树,第一次走到 \(i\) 父亲的期望时间,转移就考虑它走到它儿子的概率,方程如下
时间复杂度 \(\mathcal O(n+m)\),至此38pts
注意到 \(n-1\le m\le n+ 500\) ,感性理解一下,这表明原题中真正需要用到高斯消元的式子并不多。赛时想了一下边双缩点,但这显然没有什么用,出题人给出了一种很震撼的虚树做法
我们考虑先在原图中找出一棵 DFS 树,其中包含了 \(n-1\) 条边,接着我们依次遍历剩下的 \(m-n+1\) 条边,把每条边相邻的两个点都在 DFS 树上标记一下,作为关键点;接着,我们根据 DFS 树的连通关系,给我们标记出来的点建一棵树,我们求出这棵树的虚树(实际上是在补全lca,会多出来一些新的关键点),然后将关键点删掉,得到的新图有两个性质
- 我们就去掉了原图中所有的环
证明可以考虑反证法,假设删完后存在环,那么至少有一条边环边不在 DFS 树上,环上至少有两个关键点,关键点又被删掉了,出现矛盾
- 新图中任意一个连通块至多与两个关键点相邻
同样考虑反证法,假设存在连通块与大于两个关键点相邻,那么一定存在两个端点处的关键点有 lca,这个 lca 也是关键点并且还没有被删(被删了这几个关键点就分开了),出现矛盾
那么我们就可以对每一个连通块做树形dp,加回关键点的时候在虚树上做高消即可算出每个点的答案,时间复杂度 \(\mathcal O(m+(m-n)^3)\) ,按出题人的意思只能得 77pts
但是我们注意到,加边带来的至多 \(2(m-n)\) 个关键点再加上建虚树时作为 lca 的关键点 ,我们关键点的个数可以被卡到 \(3 (m-n)\) ,理论上无法通过此题,我不是很看的懂出题人下一步的优化是怎么做的,但是我用上面的做法加fread优化冲过去了
ZR2020 提高组十连测 Day10 T2 (树的拓扑序计数于dp中的应用)
题意
你有一个数列 \(A=(1)\),也就是一开始只有一个元素。
你要对这个数列进行 \(N\) 次操作,每次操作可以是下面的一种:
-
在 \(A\) 数列的末尾加入 \(1\) 或者 \(M\) 。
-
选择一个下标i满足 \(1\leq i < |A|\),然后选择一个数字 \(x\) 满足 \(a_i< x < a_{i+1}\) 或者 \(a_i > x > a_{i+1}\),然后将数字插入到 \(a_i\) 与 \(a_{i+1}\) 之间。
问有多少种不同的操作序列。这里两个操作序列不同,当且仅当存在某个时刻,使得操作完两个数组的内容不同。
\(1\leq N\leq 3000, 2\leq M\leq 10^8\)
题解
设 \(f_i\) 表示考虑到第 \(i\) 个操作形成了若干段时有多少种操作序列。如果该位置操作与上一段结尾相同,那么就不能插入别的数;否则就组合数计算方案数,而根据大小关系,我们可以给我们建出来的序列建出一棵树,操作顺序必须是这棵树的一个拓扑序。
而我们知道,有根树的拓扑序数量为
因而直接套树的拓扑序计数的套路,每次注意把新生成的子树大小给除上去就行了,时间复杂度 \(\mathcal O(n^2)\)
ZR2023 NOIP赛前20连测 Day5 T2 (根据所求缩小状态个数)
题意
小诗想生成一个长为 \(n\) 的序列 \(a\),她首先确定了每个位置取值的上下界 \([l_i,r_i]\),接下来对每个位置生成上下界内的任意正整数 \(a_i\in[l_i,r_i]\)。她会用以下方式评判 a 的权值:
取某一值域 \([1,n-3]\) 的可空子集 \(S\subseteq\{1,2,\cdots,n-3\}\),并计算 \(\prod_{x\in S}\gcd(a_x,a_{x+1},a_{x+2},a_{x+3})\)(在 \(S=\varnothing\) 时定义该式值为 \(1\)),并对所有子集的答案求和。
她想知道所有能生成的序列权值之和,由于答案过大,你只需告诉她答案对 \(10^9+7\) 取模后的结果。
对于所有测试点,\(4\leqslant n\leqslant 200,1\leqslant m\leqslant 100\)
题解
对于一个序列方案,它的权值即为
然后考虑 dp ,最暴力的做法就是枚举三个值进行转移,时间复杂度是 \(\mathcal O(nm^4)\) 的,无法接受
考虑我们要求的是四个数的 \(\gcd\) ,所以对于相邻的三个数 \(a,b,c\) 我们只需记录 \(\gcd(a,b,c),\gcd(b,c),c\) ,他们三个之间呈倍数关系,因此状态总数来到了 \(\mathcal O(m\log m)\) 级别,总时间复杂度降为 \(\mathcal O(nm^2\log m)\)
posted on 2023-10-27 17:58 star_road_xyz 阅读(54) 评论(0) 编辑 收藏 举报