动态规划题目合集
3n多米诺问题
\(dp[i]\) 表示前 \(i\) 列的方案数,\(dp2[i]\) 表示前 \(i\) 列但是最上面一行缺一个的方案数。
\(dp[i],dp2[i]\) 可以相互递推,而且刚好是矩阵递推。
矩阵快速幂优化。
CF809D 题解
CF15E 题解
CF17C 题解
CF79D 题解
Walk
有向无权图。求长度 \(k\) 的路径条数。
我们发现邻接矩阵的 \(k\) 次方各个元素求和就是路径条数。
矩阵快速幂。
NOIP2023 T4 题解
古城之谜
状态定义:
\(dp1[i]\) 表示前 \(i\) 个字符,结尾是一个动词加上若干辅词,分成若干个句子,所得的最小句子数和单词数。(是一个 pair)
\(dp2[i]\) 类似上方定义,但是是名词加上若干辅词。
初值:
除了 \(dp1[0]\) 的 first = second = 0,其他都是 first = second = \(+\infty\)。因为文章开头一个必须是名词,所以开头之前就是要动词以接名词。
状态转移:
求 \(dp[i]\) 时,枚举 \(j\),使得 \(s(j,i)\)(\(s[j]\sim s[i]\) 构成的字符串)是一个单词。
记 \(str=s(j,i)\)。
注:这里的 pair 取 min 是先按照句子数(first)比,first 相等按单词数单词数(second)比。\((a,b)\) 是 make_pair(a,b) 的简写,两个 pair 相加是把 first 和 second 对应相加。
-
\(str\) 是辅词,\(dp1[i]=\min(dp1[i],dp1[j-1]+(0,1))\),\(dp2[i]=\min(dp2[i],dp2[j-1]+(0,1))\);
-
\(str\) 是名词,\(dp2[i]=\min(dp2[i],dp2[j-1]+(1,1),dp1[j-1]+(0,1))\);
-
\(str\) 是动词,\(dp1[i]=\min(dp1[i],dp2[i-1]+(0,1))\)。
但是这太慢了,我们发现,枚举上一个单词的结尾时,我们不需要重新循环一遍组成单词来判断,我们可以使用 Trie 来优化这个操作。只需要把所有单词倒着插入 Trie 中,我们就可以利用 Trie 快速检查一个单词是否存在。
还要注意几个问题,我们的转移可能会导致最后一个句子里面不是名词开头,对此我们的应对方法是:枚举最后一个单词,如果是动词,就用前面的 dp2 加一个单词更新答案;如果是名词,就用 dp1 加一个单词更新答案。
知识点:
用 pair 数组 dp,可以同时记录两个答案
Trie 优化
铁球落地
先按照高度从矮到高把所有平台排序。
状态定义:
\(dpl[i]\) 表示前 \(i\) 个平台,且从第 \(i\) 个平台的左端点(开始就先下落)出发,降落到地面所需的最短时间。
\(dpr[i]\) 类似,但是是从右端点开始。
初值:
除了 \(dpl[0]=dpr[0]=0\),其他都是 \(+\infty\)。
转移:
因为 \(dpr\) 的转移显然和 \(dpl\) 类似,这里只说 \(dpl\)。
枚举 \(j:1\leq j < i\),看一下小球从左端点下落能不能落到平台 \(j\) 上面,如果能,那么进行转移:\(dpl[i]=\min(dpl[i],dpl[j]+l[i]-l[j],dpr[j]+r[j]-l[i])\),其中 \(l[i]\) 表示平台 \(i\) 的左端点 x 坐标,\(r[i]\) 表示平台 \(j\) 的右端点 x 坐标。(往左滚和往右滚两种取较小)
但是这太慢了,考虑优化。
我们发现,枚举 \(j\) 的循环其实不必要,我们可以使用线段树。
具体而言,我们计算完 \(dp[i]\) 之后,在线段树上把 \([l[i],r[i])\) 直接赋值为 \(i\),这样我们在下次查询 \(i+1\) 的左端点会落到哪里的时候,直接 \(qry(l[i+1])\) 单点查询。
当然,这也意味这我们需要离散化每个平台的左右端点坐标。
可以这么做的原因,是我们按照高度排序了,所以如果一个平台上方有另一个更高的平台,被更高平台覆盖的部分一定不会被落到。
答案还需要处理一下,因为我们没有算小球一开始下落和滚动的时间。
线段树优化
奶牛集合
换根 DP
物流运输
保安站岗
树形DP,\(dp_{i,0/1/2}\) 表示以 \(i\) 为根的子树全部覆盖,并且 \(i\) 被覆盖自己的保安/被父结点的保安/被子结点的保安 看守时的最小代价。
保安站岗弱化版
Arrays
这题蓝属实过分了吧(
\(dp[i][0/1/2]\) 表示前 \(i\) 个余 \(0/1/2\) 的方案数。
AT_dp_x
先用贪心得知 \(w+s\) 越小的越在上面。
然后 dp。
Mice and Holes
显然一个洞只会容纳连续的一段老鼠。把老鼠和洞都排序。
\(dp[i][j]\) 表示前 \(i\) 只老鼠前 \(j\) 个洞的最小总距离。
\(dp[i][j]=\displaystyle \min_{k\le c_j}\{dp[i-k][j-1]+\sum_{i-k+1\le x\le i} |P_j-p_x|\}.\)
\(P_j\) 为洞 \(j\) 的位置,\(p_x\) 为老鼠 \(x\) 的位置。
发现当 \(i\) 一定时,后面的求和可以前缀和优化。记为 \(S_{i,j}\)。发现这与 \(k\) 无关,直接提出来。
\(dp[i][j]=S_{i,j}+\min(dp[i-k][j-1]-S_{i,k})\)。
这样就可以单调队列优化。
POJ2288:
题意:给定图带点权。找一条哈密顿路径权值最大。一条路径的权值定义:设路径为 \(v_1v_2\dots v_n\),则权值为:\(\sum a_{v_i}+\sum a_{v_i}a_{v_{i+1}}\),同时,如果 \(v_i,v_{i+1},v_{i+2}\) 形成环,则权值还要加上 \(a_{v_i}a_{v_{i+1}}a_{v_{i+2}}\)。点个数 \(\le 13\)。
解法:状压 dp,\(dp[i][j][S]\) 表示去过的点状态为 \(S\),上一次到的点是 \(i\),当前在 \(j\) 的路径权值最大是多少。
关于三个点的 dp,一般可记录上一个和当前的,枚举下一个的时候统计。
EST
题意:给定一个序列 \(a\),求一个序列 \(b\),使得 \(\sum |a_i-b_i|\) 最小,且 \(b\) 可以分成恰好 \(k\) 段,每一段内的数都相等。
\(n\le 2000,k\le 25\)。
显然一段内的 \(b\) 都等于对应的 \(a\) 中这一段的中位数。
考虑 \(dp[i][j]\) 表示前 \(i\) 个分成 \(j\) 段,\(\sum|a_i-b_i|\) 最小是多少。
枚举第 \(j\) 段的开头元素 \(a[l]\),\(dp[i][j]=\min\{dp[l-1][j-1]+cost(l,i)\}\)。其中 \(cost(l,i)\) 表示 \(\sum_{x=l}^i|a_x-a_{mid}|,a_{mid}\) 是 \(a_l\sim a_i\) 的中位数。
其他部分 \(O(n^2k)\) 可以过,但是求 \(cost(l,i)\) 必须进行优化,不能暴力。
求 \(a_{mid}\),我们可以用对顶堆,同时 \(l\) 从 \(i\) 到 \(1\) 枚举可以动态求中位数。
把 \(\sum|a_x-a_{mid}|\) 分成两半:一种是 \(a_x\ge a_{mid}\) 的,一种是 \(a_x< a_{mid}\) 的。我们只需要知道这两种数的个数,和这两种数的和就行。假设 \(a_x\ge a_{mid}\) 的有 \(cbig\) 个,这种 \(a_x\) 的和是 \(sbig\),\(a_x<a_{mid}\) 类似定义。
则 \(\sum|a_x-a_{mid}|=sbig-cbig\times a_{mid}+csmall\times a_{mid}-ssmall\)。
其实我们只需要知道 \(cbig,sbig\) 就行,因为 \(csmall,ssmall\) 可以做差求出。
那如何快速求 \(cbig,sbig\)?我们可以用树状数组,这是很经典的问题。
Tower of Hay 与题解
绝世好题(只是叫这个名字而已)
部分分显然就是 \(dp[i]\) 表示前 \(i\) 个的最长长度,但是这是 \(O(n^2)\) 的。
在状态定义上面优化(这种优化一般比较巧):\(dp[i][j]\) 表示前 \(i\) 个数选的子序列,要求子序列最后一项的第 \(j\) 个二进制位是 \(1\) 的最长长度。
初始值全部设成 \(0\)。
转移的时候,先让所有 \(dp[i][j]=dp[i-1][j]\)。接着对 \(a[i]\) 所有是 \(1\) 的位 \(x\),\(dp[i][x]++\)。然后对 \(a[i]\) 所有是 \(1\) 的位 \(x\) 的 \(dp[i][x]\) 求出最大值,把这些 \(dp[i][x]\) 全部赋值成这个最大值。
262144
第一眼的想法肯定是区间 dp,但是这题不仅碍于数据范围,而且用区间 dp 也不知道两个子问题区间的长度。
既然不知道区间长度,不如直接设成 \(dp\) 的值。(下面定义成右端点也是等价的)
状态定义:\(dp[i][j]\) 表示以 \(i\) 为左端点,能合成出 \(j\) 的右端点最左是哪里。如果合成不出来就是 \(0\)。
初值 \(dp[i][a[i]]=1\),转移 \(dp[i][j]=dp[dp[i][j-1]][j-1]\)。
感觉有点像倍增了,这个转移方程正确吗?
要合成出 \(j\),显然只能 \((j-1),(j-1)\rightarrow j\),所以我们只需要搞出两个能合成出 \(j-1\) 的区间,尽可能最短即可。
定义范围 \(dp[1\sim n][1\sim 58]\),为什么是 \(58\)?因为 \(262144=2^{18}\),所以合成的数最多是 \(40+18=58\)。
Sue 的小球
类似的题:关路灯
最大价值 = 总价值 - 最小损耗
\(dp1[i][j]\) 表示从 \(j\) 直接走到 \(i\) 所有灯的最小损耗。
\(dp2[i][j]\) 表示从 \(i\sim j\) 中间一个点走到 \(j\) 再走回 \(i\) 所有灯的最小损耗。
20个问题
注意本题的多测
\(dp[S][U]\) 表示询问了 \(S\) 中的位,且得到的答案存在 \(U\) 里面,最坏情况最少要问多少次确定答案。
\(S\) 某一位为 \(0\) 表示没有询问,没有询问的位在 \(U\) 中设定为 \(0\);\(S\) 某一位为 \(1\),\(U\) 中对应位代表询问得到的答案。
预处理一个 \(cnt[S][U]\):表示有多少个数满足询问 \(S\) 中的位且答案为 \(U\)。
目标状态:\(dp[0][0]\)。
递推:若 \(cnt[S][U]=1\),则 \(dp[S][U]=0\);否则枚举 \(j\not\in S\),\(dp[S][U]\leftarrow \min(dp[S][U],\max(dp[S+2^j][U],dp[S+2^j][U+2^j]) + 1)\)。
集合选数
巧妙!
构建一个方格图来描述这个不能选的关系。对于每个数抽象为一个方格,它乘三代表的数在它右边,它乘二代表的数在它下面。于是就构造出了若干个方格图。
题目的条件等价于相邻格子不能选。
由乘法原理,我们只要求出每个方格图的方案数再乘起来就行。
而对于单个方格图,可以状压 dp,是个经典模型。
Max Correct Set
结论:一定存在一个最优解,满足如果 \(p\) 选了,\(p+x+y\) 也被选。
有了这个结论就能直接状压了。配合上:环形,不停 + x,如果超过 y 就 - y 这个理解。可以变成 \(O(x+y)\) 的环形 DP。
注意处理 \(gcd(x,y)>1\) 的情况:\(ans(n,x,y)=((g - n \% g) * ans(n / g, x / g, y / g)) + ((n \% g) * ans(n / g + 1, x / g, y / g)))\)
给定 \(n\times m\) 的网格,上面有障碍物,有多少种方法放入一个 \(1\times 2\) 的矩形。
前缀和。
分两半
给定一个序列,判断是否能通过删除若干数使得整个序列不能分成和相等的两半。
先用可行性背包 DP 出一开始能不能分成和相等的两半——如果不行,就不用删。
否则,我们考虑到如果总和是奇数,肯定不能分成两半。所以尝试删除一个奇数。而如果全部都是偶数,我们将每个数都除以二,递归再看。
2×m 哈密顿路
给定一个 \(2\times m\) 的方格,每个格子有一个锁定时间 \(a_{i,j}\),表示 \(a_{i,j}\) 秒后这个格子才开放。
初始在 \((1,1)\),每一秒可以移动一格或者原地不动。求每个格子恰好走过一遍,所需的最小时间。
玩家
你要打怪兽了,有 \(C\le 10^6\) 个金币。有 \(n\) 种战士,攻击力、血量、花费为 \(d_i,h_i,c_i\)。并且每多花 \(c_i\) 个金币,攻击力就多一个 \(d_i\)。只能选一种战士作战。
有 \(q\) 个询问,每次给出一个血量 \(H\) 攻击 \(D\) 的怪物。怪物杀掉战士需要 \(h_i/D\) 的时间,战士杀掉怪物需要 \(H/d_i\) 的时间。求杀掉怪物且战士不死的最小花费。
只要 \(h_i\times d_i\) 越大,就越容易干掉怪物。我们将 \(h_i\times d_i\) 称为战士的能力值。
鉴于 \(C\) 比较小,可以考虑对每一个花费都求出能获得的最大能力值。注意只能选一种战士作战。
\(dp[i]\) 表示恰好用 \(i\) 个金币能获得的最大能力值。初值 \(dp[c_i]=\max\{h_i\times d_i\}\)。
递推:\(dp[i\times j]=\max(dp[i\times j], dp[i]\times j)\)。
构造 1~5
给定序列 \(a_i\),要求构造序列 \(b\),满足:
-
\(b_i\in [1,5]\cap \mathbb{Z}\)。
-
若 \(a_i=a_{i+1}\),\(b_{i}\neq b_{i+1}\)。
-
若 \(a_i>a_{i+1}\),\(b_{i}> b_{i+1}\)。
-
若 \(a_i<a_{i+1}\),\(b_{i}< b_{i+1}\)。
可行性 DP:\(dp[i][j]\) 表示考虑前 \(i\) 个元素,\(b_i=j\) 是否可行。
删数位
给定一个长度 \(\le 10^5\) 的大数。可以从中挑连续的一段数位删去,将剩下的数位构成的新数统计入总和中。问这个总和是多少。(允许前导零)
考虑每一位的贡献。如果被删了,肯定没有贡献。所以贡献可以分为:只删前面 和 只删后面。
只删前面:很简单,这一位的贡献始终不变,前面共有 \(2^{x}-1\) 种方法删,所以这一位的贡献是 \((2^x-1)\times a_{x+1}\times 10^{len-x-1}.\)
只删后面:容易用递推的方法做。
倍增
给定长度 \(n\le1000\) 的序列 \(a\) 和 \(b\),\(a\) 初始全是 \(1\),\(b_i\le 1000\)。每次操作可以选一个位置 \(pos\) 和正整数 \(x\),\(a_{pos}\leftarrow (a_{pos}+\lfloor \dfrac{a_{pos}}{x}\rfloor)\)。共可以进行 \(k\le 10^6\) 次操作。
若最后 \(a_i=b_i\),可以获得 \(c_i\le 10^6\) 的收益。
求最大收益。
首先可以 \(O(n^2)\) 预处理 \(1\rightarrow x\) 所需的此时 \(f[x]\)。
然后就变成01背包模型。但是这样是 \(O(nk)\) 的,会炸。
进一步观察,其实上涨的幅度很快,根本用不到 \(k\) 次操作就能长好了。所以如果 \(k\ge \sum f[b_i]\),可以直接求和输出,这样就优化到约 \(O(12n^2)\)。
抢救
给定 \(n\) 个物品,每个物品有一个死亡时间,拯救需要的时间和拯救获得的价值。
这题很像 建筑抢修,区别是物品有各自的拯救价值。但关键点都是:按物品死亡时间升序排序。
排完序就是 DP 了。\(dp[i][j]\) 表示前 \(i\) 个物品拯救完最后一个物品时间不晚于 \(j\) 的最大收益。
覆盖
题意:给定一个颜色序列。要选定一个位置 \(pos\),不停把 \(pos\) 所在的颜色连通块改成另一个颜色。问最少需要多少次操作,可以改成全部同色。
很自然的想法就是区间 DP,\(dp[i][j]\) 表示将 \([i,j]\) 改成同色的最小操作数量。
初值 \(dp[i][i]=0\)。
若 \(i,j\) 同色,\(dp[i][j]\leftarrow \min(dp[i][j],dp[i+1][j-1]+1)\)。
同时 \(dp[i][j]\leftarrow \displaystyle \min_{k=i}^{j-1}\{dp[i][k]+dp[k][j]+1\}\)。
注意:这么写是对的,因为是选定一个位置不停改。
完成任务
有 \(n\) 个任务,第 \(i\) 个要在 \(a_i\) 时刻前做完。
有 \(m\) 个计划,第 \(i\) 个可以花 \(t_i\) 的时间让任务 \(e_i\) 增加 \(p_i\% \;\;(p_i\le 100)\) 的完成度。当一个计划的完成度 \(\ge 100\%\),这个任务就完成了。
判断是否能让每个任务完成。并且可以的话,输出执行计划的方案。\(n,m\le 10^5,e_i\le n, t_i\le 10^9\).
明显看出可以把所有任务按照 \(a_i\) 升序排序,把所有计划分成一个一个任务的来看。
对于一个任务,我们的问题变成:有 \(x\) 个计划,每一个计划有需要花费的时间和可以获得的完成度。用 \(a_i\) 的时间最多能达到多少完成度?
这不就是 01背包 吗 ?但是 \(a,t\) 的范围都很大。我们可以反着来,\(dp[i][j]\) 表示前 \(i\) 个达到 \(j\) 的百分比,至少需要多少时间。(\(j\le 200\))
回文串判断
给定字符串,求出 \([l,r]\) 内的子串包含多少个回文串。\(|S|\le 5000\)。
可行性DP:\(dp[i][j]\) 表示 \([i,j]\) 是否是回文串。询问 \([l,r]\) 内的子串,可以看作是 \(\displaystyle \sum_{i=l}^r \sum_{j=i}^r dp[i][j]\),类似二维前缀和。
回文串计数
给定字符串,求出有多少个不相交的的回文子串对。\(|S|\le 2000\)。
和上一题一样,\(dp[l][r]\) 表示 \(S[l\sim r]\) 是否是回文串。然后令 \(cnt[i]=\displaystyle\sum_{j=i}^n dp[i][j]\),此时 \(cnt[i]\) 记录以第 \(i\) 个字符开头的回文串个数。
对 \(cnt\) 求后缀和,此时 \(cnt[i]\) 记录所有开头在 \(i\) 之后的回文串个数。
枚举所有回文串 \(S[l\sim r]\),累加 \(cnt[r+1]\)。最后输出即可。
祖玛
给定一个字符串,每次可以挑一个回文子串删除,剩下的会拼起来。问最少删除几次可以删光。\(n\le 500\)
初值:\(dp[i][i]=1,dp[i][i+1]=1+(s[i]\neq s[i+1])\)。
递推:若 \(s[i]=s[j]\),\(dp[i][j]\leftarrow dp[i+1][j-1]\)。(删除 \([i+1,j-1]\) 的最后一次搭上 \(i,j\) 即可)
同时任何时候,\(dp[i][j]\leftarrow dp[i][k]+dp[k+1][j]\)。
括号涂色
给定一个匹配的括号序列,每一个括号可以不涂色或者涂两种颜色之一。每一对匹配的括号恰好有一个要涂色,相邻两个括号不能涂同色。求方案数。\(|s|\le 700\)。
\(dp[i][j][x][y]\) 表示 \(s[i\sim j]\) 且两端颜色分别为 \(x,y\) 的方案数。
倒水
有 \(n\) 个杯子,第 \(i\) 个杯子容积 \(a_i\),初始有 \(b_i\) 的水。你可以从一个杯子向另一个杯子倒水,但是 \(x\) 升水倒过去,会有 \(\dfrac{x}{2}\) 升的水损失。
对于 \(p=1\sim n\),求出经过若干次倒水后,选出 \(p\) 个杯子,其中水总和最大是多少。
显然当我们选定之后,要尽量从没选的杯子往选了的杯子里倒水。
记原本总水量为 \(B\),选了的杯子的集合为 \(S\),选了的杯子的容积之和为 \(A_S\),选了的杯子目前有 \(B_S\) 的水。
则最终的答案是 \(\min(A_S,B_S+(B-B_s)/2)=\min(A_S,B/2+B_S/2)\)。
因此我们要让 \(B_S\) 大。
\(dp[i][k][A]\) 表示前 \(i\) 个杯子,选出 \(k\) 个杯子,容积和为 \(A\) 的情况下,\(B_S\) 最大是多少。
初值 \(dp[0][0][0]=0\)。
\(dp[i][k][A]=\max(dp[i-1][k][A],dp[i-1][k-1][A-a[i]]+b[i]).\)
清除字符串
题意:每次可以删除一个字符块,求最小操作次数删完。
\(dp[i][j]\):删光 \(s[i\sim j]\) 的最小操作次数。
\(dp[i][i]=1,dp[i][i+1]=1+1\times[s[i]\neq s[j]]\)。
\(dp[i][j]\leftarrow \min(dp[i][k]+dp[k+1][j])\)。
\(dp[i][j]\leftarrow dp[i+1][j]+1\)。
\(dp[i][j]=\min(dp[i+1][k-1]+dp[k][j])\),当 \(s[i]=s[k]\)。(把 \(s[i+1\sim k-1]\) 删掉,这样 \(s[i]\) 和 \(s[k]\) 就相邻,可以视作一个字符)
异或
给定一棵树,每个点有 0/1 的点权。第 \(t\) 时刻,每个节点的点权变成第 \(t-1\) 时刻它所有直接子节点的点权的异或和。
一个时刻 \(t\) 的权重定义为 \(t\) 时刻所有节点的点权和,一个对点权的安排 \(A\) 的权重 \(F(A)\) 定义为 \(t=0\sim +\infty\) 时的权重之和。对于所有点权的安排 \(A\),求出 \(\sum F(A)\)。
观察到,第 \(1\) 时刻点权是儿子们的异或和,第 \(2\) 时刻点权是孙子们的异或和 ……
定义 \(s_i\):叶节点的 \(s=1\),其余的 \(s_u=\max\{s_{son}\}+1\)。
显然对于节点 \(u\),只有 \(0\sim s_u-1\) 时刻,它的点权可能是 \(1\)。(过了 \(s_u\),子孙深度就不够了)
假设某时刻 \(u\) 需要用到 \(cnt\) 个子孙来异或自身的贡献。下证:这些子孙的点权安排有 \(2^{cnt-1}\) 种可能使得 \(u\) 点权为 \(1\)。
将一个使得 \(u\) 点权为 \(1\) 的01序列写成一个二进制数 \(x\),\(x\) 和 \(x\bigoplus 1\) 形成一一对应,且 \(x\) 和 \(x\bigoplus 1\) 恰好有一个有贡献。所以有一半的可能使得 \(u\) 点权为 \(1\)。
既然这里有 \(2^{cnt-1}\) 种方法,算上其他的点随便安排,就有 \(2^{n-1}\) 种可能。
因此我们用 DP 求出 \(\{s\}\),然后计算 \((2^{n-1})\sum s_i\) 即可。
补括号
给定一个长度 \(m\) 的括号序列,要求在两侧补上一些字符使得变成长度 \(n\) 的合法括号序列。求方案数。\(n-m\le 2000\)。
预处理 \(dp[i][j]\):\(i\) 个字符,一共左括号比右括号多 \(j\) 个,且每一个前缀左括号都不比右括号数量少,的括号序列有多少个。
\(dp[i][j]=dp[i-1][j+1]+dp[i-1][j-1]\),若 \(j=0\) 就 \(=dp[i-1][j+1]\)。
然后枚举放在左侧的括号序列长度、左括号比右括号多的数量。
乘一下累加即可。
路径
给出 \(n\) 个点,\(m\) 条边的有向图。
求出一条任意起点、途中不能经过之前走过的点,并且包含 \(k\) 个点的权值最小的路径,如果不存在就输出 \(-1\) 。
边 \((u, v)\)经过点 \(x\),当且仅当 \(min(u,v) < x < max(u,v)\) 。
\(1 \leq n,k \leq 80, 0\leq m\leq 2000, 1\leq u_i, v_i \leq n, 1\leq c_i\leq 1000\)。
走一条边,就会把能走的区间分开。考虑 \(dp[i][l][r][0/1]\) 表示当前已经经过了 \(i\) 个点,可以走的区间是 \((l,r)\),0表示当前在 \(l\),1表示当前在 \(r\)。
初值:\(dp[1][0][i][1]=dp[1][i][n+1][0]=0\)。
递推:适合刷表法。\(dp[i][l][r][0/1]+edg\_l.val\rightarrow dp[i + 1][l][edg\_l.to][1],dp[i+1][edg_l.to][r][0]\),\(dp[i][l][r][0/1]+edg\_r.val\rightarrow dp[i + 1][l][edg\_r.to][1],dp[i+1][edg_r.to][r][0]\)。
答案:\(\displaystyle\min_{0\le l<r\le n+1}\{dp[k][l][r][0/1]\}\)。
注意是开区间。
角斗场
有 \(n\) 个人,每个人有初始血量。每个回合,所有还活着的人会同时对场上其余人造成 \(1\) 伤害,然后血量 \(\le 0\) 的死掉。
现在要你指派每个人的初始血量,要求在 \([1,x]\) 之间。然后进行若干回合,使得最后无人生还。求方案数。
\(dp[i][j]\) 表示有 \(i\) 个人,最大血量为 \(j\) 的方案数。
若 \(i-1\ge j\),随便安排人的血量即可。\(dp[i][j]=j^i-(j-1)^i\)。
否则,枚举剩下的人的个数,并且此时剩余最大血量为 \(j-i+1\)。所以 \(dp[i][j]=\sum dp[k][j-i+1]\times (i-1)^{i-k}\times C_i^k\)。
极差
给定序列 \(a\),对其重排,使得 \(\sum\limits_{i=1}^{n} (\max\limits_{j=1}^ia_j-\min\limits_{j=1}^i a_j)\) 最小。
观察发现,最后一个位置必然是最大值或者最小值。
于是对 \(a_i\) 先升序排序,\(dp[i][j]\) 表示 \(a_i\sim a_{i+j-1}\) 的答案。
\(dp[i][j]=\min(dp[i+1][j],dp[i][j-1])+a_j-a_i\)。
二叉树
求 \(n\) 个结点,高度至少为 \(h\) 的二叉树的个数。(一个儿子的左右不同也算不一样)
\(dp[i][j]\):$$ 个节点,高度不高于 \(j\) 的个数。
初值 \(dp[i][0]=0,dp[0][i]=1\)。
递推 \(dp[i][j]=\sum dp[k][j-1]\times dp[i-1-k][j-1]\)。
答案 \(dp[n][n]-dp[n][h-1]\)。
蚂蚁
一个数轴的正半轴上有 \(n\le 2e5\) 个传送门,第 \(i\) 个传送门在位置 \(x_i\)。并给定他们的初始状态,\(t_i=0\) 代表初始关闭,否则初始开启。
有一只蚂蚁在 \(0\),以每秒 1 单位的速度向右走。如果踩到一个开启状态的传送门,就会被传送到 \(y_i,y_i<x_i\),且传送门关闭;如果踩到一个关闭状态的传送门,传送门开启。
问蚂蚁走到 \(x_n+1\) 要多久。
观察:当蚂蚁位于 \(x_i+1\) 时,传送门 \(1\sim i\) 都处于开启状态。(自证)
\(dp[i]\) 表示当 \(1\sim i\) 传送门都开启,且蚂蚁处在 \(x_i\) 时,再走回 \(x_i\) 需要多久。
\(dp[i]=(x_i-y_i)+\sum\limits_{j=lft_i}^{i-1}dp[j]\)。其中 \(lft_i\) 为 \(>y_i\) 的位置小的传送门,也就是蚂蚁传送之后第一个碰到的传送门。
答案是 \(x_n+1+\sum\limits_{i\in Active}dp[i]\),\(Active\) 是所有初始开启的传送门集合。
字符串涂色
给定一个字符串,你要将每个位置涂色。如果两个相邻位置不同色,就可以交换这两个位置的字符。问最少涂多少种不同的颜色,可以使经过若干次交换后字符串升序排列。
升序排列:和逆序对有关。
即对于 \(i<j,s_i>s_j\) 要 \(clr_i\neq clr_j\)。
令 \(dp[i]\) 表示 \([i,n]\) 中以 \(i\) 开头的最长下降子序列长度,发现如果位置 \(i\) 用 \(dp[i]\) 染色,就可以做到。证明不难。
Star MST
\(dp[i][j]\) 表示加入了 \(i\) 个点,且这些点到 \(1\) 号点的最大距离为 \(j\) 的构图方案数。
叛徒
结论:
-
最坏情况肯定是初始叶子结点变成叛徒。否则下移更优。
-
最终叛变的是一颗子树。
令 \(f(i)\) 表示以 \(i\) 为根的子树中 \(i\) 不叛变的最小 \(x\)。
从简单情况考虑:若 \(i\) 只有一个儿子 \(u\),怎么求?
\(f[i]=\min(f[u],sz[u]/(sz[i]-1))\)。(这里的 \(sz[u]\) 其实等于 \(sz[i]-1\))
(要么 \(u\) 不叛变,要么即使 \(u\) 叛变了都不能使 \(i\) 叛变)
而若 \(i\) 有很多个儿子,\(f[i]=\displaystyle\max_{u\in son[i]}\{\min(f[u],sz[u]/(sz[i]-1))\}\)。
Black and White Tree
首先考虑链上的情况。发现 A 可以放在第二个位置,此时 B 必须放在第一个位置。然后问题规模 -2。
到树上,如果存在一个结点,有 \(\ge 2\) 个儿子是叶子,A 必胜;否则找到一个叶子结点 \(l\) 和他父亲 \(fl\)。
先手下在 \(fl\),后手必须下在 \(l\),然后问题规模 -2。
(这只是略证)
Subtree
树形 DP + 前后缀积优化。
Book of Evil
树形 DP 求出 \(f_1[i],f_2[i]\):子树内和子树外 \(i\) 距离最近是哪里。
三边
必然是一条直径 + 到直径最远的点。
先找出一条直径,再从直径上每个点向直径外搜索。
Neko Rules the Catniverse
给定 \(n,k,m\),你需要求有多少个大小为 \(k\) 的序列 \(a\) 满足如下三个条件:
- 任意两个元素其权值不同。
- 对于任意 \(i\) 满足 \(1\le i\le k\) 有 \(1\le a_i\le n\)。
- 对于任意 \(i\) 满足 \(2\le i\le k\) 有 \(a_i\le a_{i-1}+m\)。
答案对 \(10^9+7\) 取模。
\(1\le n\le 10^9\),\(1\le k\le \min(n,12)\),\(1\le m\le 4\)。
正常想法是按照 \(a_1\sim a_n\) 的顺序 DP,但是发现这么做不了。究其原因是需要记录前面用过哪些数,会爆掉。
所以我们尝试按照从大到小的顺序 DP,相当于往一个序列不断插入更小的数。这样子可以保证插入顺序和数的大小顺序都有。
容易发现,保证每次插入后都合法的操作序列 与 原本的序列 可以一一对应。
一个简单的想法是 \(dp[i][j]\) 表示考虑 \(i\sim n\) 的所有数,已经插入的 \(j\) 个数的方案数。但是我们发现转移的时候无法判断 \(\le i-1+m\) 的位置有哪些。注意到 \(m\) 的范围很小,可以状压。
\(dp[i][j][S]\) 表示考虑了 \(i\sim n\) 的所有数,插入了 \(j\) 个数,同时如果 \(bit\in S\),则 \(i+bit\) 在序列里,的方案数。
然后可以 \(O(1)\) 转移。
但是 \(i\) 这一维是 \(10^9\) 的,需要优化。我们上一个矩阵快速幂优化就行了。
Petya and Arrays
按照数组顺序 DP 难做。
考虑另一个数组 \(s_0=0,s_1\sim s_n\) 为 \(a_i\) 模 \(p\) 意义下的前缀和。因为每个数都是 \(1\sim p\) 的,所以 \(s\) 与 \(a\) 一一对应。
而限制转移到 \(s\) 上,就可以总结为:
-
\(s_{i+1}-s_i\neq A\bmod p\);
-
\(s_0\sim s_n\) 各不相同。
观察到如果构图 \(i\rightarrow (i+A)\mod p\),最终会形成若干个环。最终要求就相当于在环上不能有相邻的数。
于是可以改换枚举顺序:按照环枚举。基础想法 \(dp[i][j]\) 表示前 \(i\) 个环选了 \(j\) 个数,但是发现无法保证每个环上没有相邻的。升维,\(dp[i][j][k]\) 表示前 \(i\) 个环选 \(j\) 个一共有 \(k\) 个相邻位置在环上也是相邻的。
因为环的个数可能比较多,也需要矩阵快速幂。
Colorful Maze
注意到受伤两次就死,所以我们实际只需要一个变量来记录目前已知的危险颜色——如果知道了两种颜色是危险的,必定死了不合法。
但是安全格子是需要记录的,可以采用状压。\(dp[i][j][S][d]\) 表示走到 \((i,j)\) 格子,已经确认了 \(S\) 中颜色安全,已知颜色 \(d\) 危险(\(d=7\) 就表示没遇到过危险格子)的胜利概率。
转移时枚举与它相邻的位置 \((x,y)\),看 \((x,y)\) 的胜率取 \(\max\)。
-
\((x,y)\) 是终点,胜率为 \(1\)。
-
\((x,y)\) 是已知的危险颜色或者障碍物,胜率为 \(0\)。
-
\((x,y)\) 是已知的安全颜色,胜率为 \(dp[x][y][S][d]\)。
-
\((x,y)\) 是未知颜色。胜率为 \((1-trap[clr])dp[x][y][S+clr][d]+[d=7]\cdot trap[clr]dp[x][y][S][clr]\)。
然而还有一个问题:DP 顺序。这其实用记忆化搜索就行了,从终点开始。
寿司晚宴
两个人各从 \(\{2\sim n\}\) 选一个子集,要求从这两个子集里各任意选一个数,都必须是互质的。允许空集。求方案数。\(n\le 500\)。
容易想到要记录两个子集各自包含哪些质因数。对于 \(n\le 30\) 的可以直接这样做状压 DP,但是 \(n\le 500\) 的质数太多了。
思考发现,其实对于 \(\ge 23\) 的质数,不可能同时包含两个。把质数分成小质数(\(\le 19\))和大质数(\(\ge 23\)),每个数至多包含一个大质数。所以可以把所有数按照包含的大质数分类,没有大质数的作特殊一类。
发现除了特殊一类,每一类内的数都不可能存在两个数分给两个人(所有数要么不选,要么只会属于一个人)。那它们对答案的贡献可以直接搞个快速幂求出来,我们还是只需要处理特殊一类。而这一类不包含大质数,沿用上面状压 DP 的方法即可。
Mx 的组合数:UOJ86
看到 \(10^{30}\) 肯定不能跟正常枚举有关系。考虑把数改成 \(p\) 进制的进行数位 DP。
\(x=(x_t\cdots x_1)_p,n=(n_t\cdots n_1)_p,r=(r_t\cdots r_1)_p\)。因为 \(n,r\) 实在是太大了,面对这种模数是小质数而 \(x,n\) 很大的,尝试使用卢卡斯定理。
\({n\choose x}={{n/p}\choose {x/p}}\cdot {{n\bmod p}\choose {x\bmod p}}\bmod p\)。发现在 \(p\) 进制下相当于去掉了末位。于是发现 \({n\choose x}={\prod {{n_i}\choose {x_i}}}\bmod p\)。
数位 DP 都是从高到低的。\(dp[i][j][f]\) 表示填了 \(x_t\sim x_i\),\(\displaystyle\prod_{a=i}^{t}{n_a\choose x_a}=j\),\(f=0\) 表示目前 \(x\) 填的数还和 \(r\) 相等,\(f=1\) 表示 \(x\) 已经 \(<r\)。
但是这个复杂度是 \(O(p^2\cdot \log_{p}^r)\) 的,会超时。这个优化就非常神奇了。
取 \(p\) 的原根,更换状态描述 \(dp[i][j][f]\):其他都相同,但是 \(\displaystyle\prod_{a=i}^{t}{n_a\choose x_a}=g^j\)。
这个时候我们发现如果从 \(j\) 出发,枚举下一位贡献了 \(g^k\),就会转移到 \(dp[i+1][j+k][?]\)。从 \(j,k\) 一起转移到 \(j+k\),这让我们想到卷积。而确实我们如果把 \(dp[i][][f]\) 视作多项式,就可以用一次卷积得到 \(dp[i+1][][f]\)。
还有一个要注意的就是 \(dp[i][0][f]\) 无法 \(j+k\) 求出,但是我们可以通过总方案数减去 \(dp[i][1\sim p-1][f]\) 得到。
因此复杂度降低为 \(O(p\log p\cdot \log_{p}^r)\)。
poj1090:Chain
观察发现,操作序列构成格雷码。而且每个操作序列都会先走到 \(00\cdots 0\)。
反过来求 \(00\cdots 0\) 走到一个 \((g_k\cdots g_1)_2\)。考虑格雷码一个常见的构造方式:一位的格雷码排序为 \(0,1\),记 \(n\) 位格雷码排序为 \(f(n)\),则 \(n+1\) 位的一种格雷码可以表示为 \(0+f(n),1+rev(f(n))\)。
发现按照这种格雷码,就是最优操作序列。问题变为给定一个二进制数求在格雷码中的排序。
这里有一个数学结论:第 \(n\) 个格雷码 \(=n\bigoplus [\dfrac{n}{2}]\)。(格雷码的显示构造)
推论:把 \(n\) 也写成 \(k\) 为二进制数。则有
反推可得 \(n_i=\bigoplus_{j=i}^{k}g_j\)。
TopC13457:BoardFolding
给定一张方格表,每个格子有不同的颜色。可以沿着格子线横着折或者竖着折,要求必须是小的那边折到大的那边,而且折叠对应的位置必须颜色相同。问任意次操作之后可以得到哪些子矩阵?
一个基础的想法是求一个 \(f[u][d][l][r]\),表示上下左右的边分别是 \(u,d,l,r\) 的子矩阵是否可以被折叠出来。不过是 \(O(n^4)\) 的。
这里观察到一个性质:横着折和竖着折之间是独立的,也就是判断 \((u,d,l,r)\) 是否可行,只需要判断 \((u,d)\) 是否可行和 \((l,r)\) 是否可行。
那么只用求 \(f[u][d]\) 和 \(F[l][r]\),于是降到 \(O(n^2)\)。
到这里可以通过原题,但是我们还能继续优化!
观察到 \((u,d)\) 可行 \(\iff\) \((1,d)\) 可行和 \((u,n)\) 可行。进一步只需要做四次一维 DP,优化到 \(O(n)\)。判断能否翻折用 manacher。
GYM100286C:Clock
就是给定一个很多根针的钟和之间的联动关系,求从 \(S\) 时刻调到 \(T\) 时刻要多久。(允许逆时针调)
首先发现可以把问题转化为从 \(0\) 时刻调到 \(T-S\) 时刻,就是把 \(S\) 视作起始时间。
把针 \(i\) 记作 \(p_i\)。\(p_i\) 有 \(d_{i-1}\) 格(\(\dfrac{1}{d_{i-1}}\) 圈),每走一格 \(p_{i-1}\) 就走一圈。
所以我们说 \(p_i\) 现在指向 \(x\) 点,不如把它转成指向 \([\dfrac{x}{d_{i-1}}]\) 格。注意这里的取整,因为被取整掉的部分是 \(p_{1}\sim p_{i-1}\) 的事情。
于是给每个 \(p_i\) 求一个 \(b_i=[\dfrac{x}{d_{i-1}}]\)。目的就是让 \(p_i\) 最终指向 \(b_i\)。
因为从高位到低位没有影响,因此如果我们从高到低 DP 就是无后效性。粗略的想法是 \(dp[i]\) 表示调完 \(p_{n}\sim p_i\) 使得它们都指向各自的 \(b\)。
但这是不行的,因为 \(i-1\sim 1\) 的针会对 \(p_n\sim p_i\) 产生影响,最优解很有可能 \(p_i\) 没有指向 \(b_i\),而是在 \(1\sim i-1\) 的针调的时候顺路调了。
好像卡住了?
仔细思考,\(p_i\) 最终必须指向 \(b_i\)。而如果 \(dp[i]\) 结束的状态里,\(p_i\) 指向 \(\le b_i-1\),\(p_1\sim p_{i-1}\) 要让 \(p_i\) 动必须转一圈,肯定不如 \(p_i\) 动一格;\(p_i\) 指向 \(\ge b_i+2\) 也是同理。所以唯一另外的可能就是 \(p_i\) 指向 \(b_i+1\),然后 \(p_1\sim p_{i-1}\) 变小,让 \(p_i\) 退位。
于是得出结论:当 \(p_n\sim p_i\) 调好的时候,\(p_i=b_i/b_i+1\)。
\(dp[i][0/1]\) 表示调好 \(p_n\sim p_i\),且此时 \(p_i=b_i/b_i+1\) 的最小移动量。
\(dp[i][0]=\min(dp[i+1][0]+b_i\cdot a_i,dp[i+1][1]+(d_{i-1}-b_i)\cdot a_i)\):如果 \(p_{i+1}\) 指向 \(b_{i+1}\),\(p_i\) 就从 \(0\) 正着调,否则反着调。
\(dp[i][1]=\min(dp[i+1][0]+(b_i+1)\cdot a_i,dp[i+1][1]+(d_{i-1}-(b_i+1))\cdot a_i)\):同理。
修正表达式
题意:给定三个数 \(1\le a,b,c\le 10^{9}\),一次操作为选一个数进行插入或删除或修改 某个数位。要求任何时刻 \(a,b,c\) 无前导零,求使得 \(a+b=c\) 的最小代价。
考虑把最终的 \(a,b,c\) 看作三个数组,每个位置储存一个数位。按照 \(c,a,b\) 的顺序处理每个数组,每次处理有插入(插入一个任意数到数组末尾)、删除(跳过当前对应数的数位)、修改(修改当前对应数的数位并加入数组末尾)和啥也不干(直接把当前对应数的数位加到末尾)。
\(dp[pt][D][A][B][C][S][Lim]\):\(pt\) 表示当前要对 \(pt\) 的数组(\(pt=a/b/c\))操作,\(D\) 表示目前已经填好的数位 \(c-a-b\) 是多少,\(A,B,C\) 分别记录当前指向 \(a,b,c\) 的哪一个数位,\(S\) 是一个 \(2^3\) 的状态用于描述 \(a,b,c\) 目前是否是前导零,\(Lim\) 表示接下来还允许 \(Lim\) 次插入(为了保证不会一直选择插入而死循环)。
\(pt\) 是 \(3\),\(A,B,C\) 是 \(9\),\(S\) 是 \(8\),而 \(D\le 10\):如果 \(D\) \(>10\),随着数位的后移,\(D\) 这个差距后面是无法挽回的。\(Lim\) 显然 \(\le 30\)。
转移时枚举 \(pt\) 这一位的操作/要加入或修改成什么数字,记得 \(D\) 在转移时要 \(\times 10\)。
CF710E: Generate a String
初始一个 \(0\),每次可以加减 \(1\)(给定代价 \(x\))或者 \(\times 2\)(给定代价 \(y\)),求变成 \(n\) 的最小代价。
一个 \(O(n\log n)\) 的想法是最短路,\(i\rightarrow i-1,i+1,2i\),点的个数开到 \(2n\)。但是这跑不过原题。
注意到 \(-1\) 操作不会连续进行两次,因为 \(-1\) 前面有 \(\times 2\)(不可能 \(+1-1\)),不如在 \(\times 2\) 之前 \(-1\),效果是一样的。
所以等价于三种操作:\(i\rightarrow i+1,2i,2i-1\)。直接按照这个 DP 即可。
AGC012E: Camel and Osase
给定 \(n\) 个绿洲,第 \(i\) 个绿洲的坐标为 \(x_i\) ,保证 \(-10^{9}\le x_1<x_2...<x_n\le 10^9\)
现在有一个人在沙漠中进行旅行,他初始的背包的水容积为 \(V\) 升,同时他初始拥有 \(V\) 升水 ,每次到达一个绿洲时,他拥有的水的量将自动重置为容积上限(可以使用多次)。他现在可以选择两个操作来进行旅行:
\(1.\) 走路,行走距离为 \(d\) 时,需要消耗 \(d\) 升水。清注意,任意时刻你拥有的水的数量不能为负数。
\(2.\) 跳跃,令 \(v\) 为你当前拥有的水量,若 \(v>0\),则你可以跳跃至任意一个绿洲,然后重置容积上界和所拥有的水量为 \(v/2\) (向下取整)。
对于每一个 \(i\) 满足 \(1\le i\le n\) ,你需要求解,当你在第 \(i\) 个绿洲作为起点时,你能否依次遍历其他所有绿洲。如果可以,输出 Possible
,否则输出 Impossible
。
容易观察到每次跳跃后水量折半,跳跃次数不会太多,一共 \(\log V<20\) 次。
因此可以预处理出 \(l[i][j],r[i][j]\),表示绿洲 \(i\) 有 \(\dfrac{V}{2^j}\) 的水,最左(右)能去到哪个绿洲。
看作 \(\log V\) 层,第 \(i\) 层对应初始水量 \(V/2^i\),注意要给 \(0\) 水分保留一层。每一层有 \(n\) 条线段(\([l[i][j],r[i][j]]\))。对于 \(i\) 的答案,就是固定第 \(0\) 层选择某条线段,判断从 \(1\sim \log V\) 层能不能选出线段覆盖 \(1\sim n\)。
用状压 DP 实现,\(dpl[S]\) 表示从 \(S\) 中的层选择线段,左端能连续覆盖到哪里,比如选出线段 \([1,4],[3,5],[7,8]\) 就是连续覆盖到 \(5\)。\(dpr[S]\) 就是右端连续覆盖到哪里。
直接暴力判断答案,就是枚举 \(i\leftarrow 1\sim n\),然后枚举 \(S\)(注意不包含第 \(0\) 位了),判断是否存在 \(dpl[S]\ge l[0][i]-1\) 和 \(dpr[U-S]\le r[0][i]+1\)。
但是这样是 \(O(n^2)\) 的。我们发现如果给第 \(0\) 层的线段去重的话,留下来的线段应当不超过 \(\log V\) 条:如果超过了 \(\log V\) 条,说明有 \(>\log V\) 个绿洲,即使用最多的水量也无法互相到达,所以这种情况答案一定全都是 Impossible。
于是留下 \(\log V\) 条第 \(0\) 层的线段,在枚举第 \(0\) 层线段的复杂度就变成 \(O(\log V)\),总复杂度 \(O(n+V\log V)\)。
TopC10664: RowGame
题意:给定长度 \(n\) 的数组,从 \(1\) 出发,每次选择一个位置 \(x\) 走到 \(x\),收获分数为 \(x\) 到原本位置的数和。走 \(k\) 步,求最大分数。\(n\le 50,k\le 4\times 10^8\)。