DP 题目合集

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 = +。因为文章开头一个必须是名词,所以开头之前就是要动词以接名词。

状态转移:

dp[i] 时,枚举 j,使得 s(j,i)s[j]s[i] 构成的字符串)是一个单词。
str=s(j,i)

注:这里的 pair 取 min 是先按照句子数(first)比,first 相等按单词数单词数(second)比。(a,b) 是 make_pair(a,b) 的简写,两个 pair 相加是把 first 和 second 对应相加。

  1. str 是辅词,dp1[i]=min(dp1[i],dp1[j1]+(0,1))dp2[i]=min(dp2[i],dp2[j1]+(0,1))

  2. str 是名词,dp2[i]=min(dp2[i],dp2[j1]+(1,1),dp1[j1]+(0,1))

  3. str 是动词,dp1[i]=min(dp1[i],dp2[i1]+(0,1))

但是这太慢了,我们发现,枚举上一个单词的结尾时,我们不需要重新循环一遍组成单词来判断,我们可以使用 Trie 来优化这个操作。只需要把所有单词倒着插入 Trie 中,我们就可以利用 Trie 快速检查一个单词是否存在。

还要注意几个问题,我们的转移可能会导致最后一个句子里面不是名词开头,对此我们的应对方法是:枚举最后一个单词,如果是动词,就用前面的 dp2 加一个单词更新答案;如果是名词,就用 dp1 加一个单词更新答案。

知识点:
用 pair 数组 dp,可以同时记录两个答案
Trie 优化

铁球落地

先按照高度从矮到高把所有平台排序。

状态定义:

dpl[i] 表示前 i 个平台,且从第 i 个平台的左端点(开始就先下落)出发,降落到地面所需的最短时间。

dpr[i] 类似,但是是从右端点开始。

初值:

除了 dpl[0]=dpr[0]=0,其他都是 +

转移:

因为 dpr 的转移显然和 dpl 类似,这里只说 dpl

枚举 j:1j<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

物流运输

tj

保安站岗

树形DP,dpi,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]=minkcj{dp[ik][j1]+ik+1xi|Pjpx|}.

Pj 为洞 j 的位置,px 为老鼠 x 的位置。

发现当 i 一定时,后面的求和可以前缀和优化。记为 Si,j。发现这与 k 无关,直接提出来。

dp[i][j]=Si,j+min(dp[ik][j1]Si,k)

这样就可以单调队列优化。

POJ2288:

题意:给定图带点权。找一条哈密顿路径权值最大。一条路径的权值定义:设路径为 v1v2vn,则权值为:avi+aviavi+1,同时,如果 vi,vi+1,vi+2 形成环,则权值还要加上 aviavi+1avi+2。点个数 13

解法:状压 dp,dp[i][j][S] 表示去过的点状态为 S,上一次到的点是 i,当前在 j 的路径权值最大是多少。

关于三个点的 dp,一般可记录上一个和当前的,枚举下一个的时候统计。

EST

题意:给定一个序列 a,求一个序列 b,使得 |aibi| 最小,且 b 可以分成恰好 k 段,每一段内的数都相等。

n2000,k25

显然一段内的 b 都等于对应的 a 中这一段的中位数。

考虑 dp[i][j] 表示前 i 个分成 j 段,|aibi| 最小是多少。

枚举第 j 段的开头元素 a[l]dp[i][j]=min{dp[l1][j1]+cost(l,i)}。其中 cost(l,i) 表示 x=li|axamid|,amidalai 的中位数。

其他部分 O(n2k) 可以过,但是求 cost(l,i) 必须进行优化,不能暴力。

amid,我们可以用对顶堆,同时 li1 枚举可以动态求中位数。

|axamid| 分成两半:一种是 axamid 的,一种是 ax<amid 的。我们只需要知道这两种数的个数,和这两种数的和就行。假设 axamid 的有 cbig 个,这种 ax 的和是 sbigax<amid 类似定义。

|axamid|=sbigcbig×amid+csmall×amidssmall

其实我们只需要知道 cbig,sbig 就行,因为 csmall,ssmall 可以做差求出。

那如何快速求 cbig,sbig?我们可以用树状数组,这是很经典的问题。

Tower of Hay 与题解

绝世好题(只是叫这个名字而已)

部分分显然就是 dp[i] 表示前 i 个的最长长度,但是这是 O(n2) 的。

在状态定义上面优化(这种优化一般比较巧):dp[i][j] 表示前 i 个数选的子序列,要求子序列最后一项的第 j 个二进制位是 1 的最长长度。

初始值全部设成 0

转移的时候,先让所有 dp[i][j]=dp[i1][j]。接着对 a[i] 所有是 1 的位 xdp[i][x]++。然后对 a[i] 所有是 1 的位 xdp[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][j1]][j1]

感觉有点像倍增了,这个转移方程正确吗?

要合成出 j,显然只能 (j1),(j1)j,所以我们只需要搞出两个能合成出 j1 的区间,尽可能最短即可。

定义范围 dp[1n][158],为什么是 58?因为 262144=218,所以合成的数最多是 40+18=58

Sue 的小球

类似的题:关路灯

最大价值 = 总价值 - 最小损耗

dp1[i][j] 表示从 j 直接走到 i 所有灯的最小损耗。

dp2[i][j] 表示从 ij 中间一个点走到 j 再走回 i 所有灯的最小损耗。

20个问题

注意本题的多测

dp[S][U] 表示询问了 S 中的位,且得到的答案存在 U 里面,最坏情况最少要问多少次确定答案。

S 某一位为 0 表示没有询问,没有询问的位在 U 中设定为 0S 某一位为 1U 中对应位代表询问得到的答案。

预处理一个 cnt[S][U]:表示有多少个数满足询问 S 中的位且答案为 U

目标状态:dp[0][0]

递推:若 cnt[S][U]=1,则 dp[S][U]=0;否则枚举 jSdp[S][U]min(dp[S][U],max(dp[S+2j][U],dp[S+2j][U+2j])+1)

集合选数

巧妙!

构建一个方格图来描述这个不能选的关系。对于每个数抽象为一个方格,它乘三代表的数在它右边,它乘二代表的数在它下面。于是就构造出了若干个方格图。

题目的条件等价于相邻格子不能选。

由乘法原理,我们只要求出每个方格图的方案数再乘起来就行。

而对于单个方格图,可以状压 dp,是个经典模型。

Max Correct Set

结论:一定存在一个最优解,满足如果 p 选了,p+x+y 也被选。

有了这个结论就能直接状压了。配合上:环形,不停 + x,如果超过 y 就 - y 这个理解。可以变成 O(x+y) 的环形 DP。

注意处理 gcd(x,y)>1 的情况:ans(n,x,y)=((gn%g)ans(n/g,x/g,y/g))+((n%g)ans(n/g+1,x/g,y/g)))

障碍物多米诺

给定 n×m 的网格,上面有障碍物,有多少种方法放入一个 1×2 的矩形。

前缀和。

分两半

给定一个序列,判断是否能通过删除若干数使得整个序列不能分成和相等的两半。

先用可行性背包 DP 出一开始能不能分成和相等的两半——如果不行,就不用删。

否则,我们考虑到如果总和是奇数,肯定不能分成两半。所以尝试删除一个奇数。而如果全部都是偶数,我们将每个数都除以二,递归再看。

2×m 哈密顿路

给定一个 2×m 的方格,每个格子有一个锁定时间 ai,j,表示 ai,j 秒后这个格子才开放。

初始在 (1,1),每一秒可以移动一格或者原地不动。求每个格子恰好走过一遍,所需的最小时间。

玩家

你要打怪兽了,有 C106 个金币。有 n 种战士,攻击力、血量、花费为 di,hi,ci。并且每多花 ci 个金币,攻击力就多一个 di只能选一种战士作战。

q 个询问,每次给出一个血量 H 攻击 D 的怪物。怪物杀掉战士需要 hi/D 的时间,战士杀掉怪物需要 H/di 的时间。求杀掉怪物且战士不死的最小花费。

只要 hi×di 越大,就越容易干掉怪物。我们将 hi×di 称为战士的能力值。

鉴于 C 比较小,可以考虑对每一个花费都求出能获得的最大能力值。注意只能选一种战士作战。

dp[i] 表示恰好i 个金币能获得的最大能力值。初值 dp[ci]=max{hi×di}

递推:dp[i×j]=max(dp[i×j],dp[i]×j)

构造 1~5

给定序列 ai,要求构造序列 b,满足:

  1. bi[1,5]Z

  2. ai=ai+1bibi+1

  3. ai>ai+1bi>bi+1

  4. ai<ai+1bi<bi+1

可行性 DP:dp[i][j] 表示考虑前 i 个元素,bi=j 是否可行。

删数位

给定一个长度 105 的大数。可以从中挑连续的一段数位删去,将剩下的数位构成的新数统计入总和中。问这个总和是多少。(允许前导零)

考虑每一位的贡献。如果被删了,肯定没有贡献。所以贡献可以分为:只删前面 和 只删后面。

只删前面:很简单,这一位的贡献始终不变,前面共有 2x1 种方法删,所以这一位的贡献是 (2x1)×ax+1×10lenx1.

只删后面:容易用递推的方法做。

倍增

给定长度 n1000 的序列 aba 初始全是 1bi1000。每次操作可以选一个位置 pos 和正整数 xapos(apos+aposx)。共可以进行 k106 次操作。
若最后 ai=bi,可以获得 ci106 的收益。

求最大收益。

首先可以 O(n2) 预处理 1x 所需的此时 f[x]

然后就变成01背包模型。但是这样是 O(nk) 的,会炸。

进一步观察,其实上涨的幅度很快,根本用不到 k 次操作就能长好了。所以如果 kf[bi],可以直接求和输出,这样就优化到约 O(12n2)

抢救

给定 n 个物品,每个物品有一个死亡时间,拯救需要的时间和拯救获得的价值。

这题很像 建筑抢修,区别是物品有各自的拯救价值。但关键点都是:按物品死亡时间升序排序

排完序就是 DP 了。dp[i][j] 表示前 i 个物品拯救完最后一个物品时间不晚于 j 的最大收益。

覆盖

题意:给定一个颜色序列。要选定一个位置 pos,不停把 pos 所在的颜色连通块改成另一个颜色。问最少需要多少次操作,可以改成全部同色。

很自然的想法就是区间 DP,dp[i][j] 表示将 [i,j] 改成同色的最小操作数量。

初值 dp[i][i]=0

i,j 同色,dp[i][j]min(dp[i][j],dp[i+1][j1]+1)

同时 dp[i][j]mink=ij1{dp[i][k]+dp[k][j]+1}

注意:这么写是对的,因为是选定一个位置不停改

完成任务

n 个任务,第 i 个要在 ai 时刻前做完。

m 个计划,第 i 个可以花 ti 的时间让任务 ei 增加 pi%(pi100) 的完成度。当一个计划的完成度 100%,这个任务就完成了。

判断是否能让每个任务完成。并且可以的话,输出执行计划的方案。n,m105,ein,ti109.

明显看出可以把所有任务按照 ai 升序排序,把所有计划分成一个一个任务的来看。

对于一个任务,我们的问题变成:有 x 个计划,每一个计划有需要花费的时间和可以获得的完成度。用 ai 的时间最多能达到多少完成度?

这不就是 01背包 吗 ?但是 a,t 的范围都很大。我们可以反着来,dp[i][j] 表示前 i 个达到 j 的百分比,至少需要多少时间。(j200

回文串判断

给定字符串,求出 [l,r] 内的子串包含多少个回文串。|S|5000

可行性DP:dp[i][j] 表示 [i,j] 是否是回文串。询问 [l,r] 内的子串,可以看作是 i=lrj=irdp[i][j],类似二维前缀和。

回文串计数

给定字符串,求出有多少个不相交的的回文子串对。|S|2000

和上一题一样,dp[l][r] 表示 S[lr] 是否是回文串。然后令 cnt[i]=j=indp[i][j],此时 cnt[i] 记录以第 i 个字符开头的回文串个数。

cnt 求后缀和,此时 cnt[i] 记录所有开头在 i 之后的回文串个数。

枚举所有回文串 S[lr],累加 cnt[r+1]。最后输出即可。

祖玛

给定一个字符串,每次可以挑一个回文子串删除,剩下的会拼起来。问最少删除几次可以删光。n500

初值:dp[i][i]=1,dp[i][i+1]=1+(s[i]s[i+1])

递推:若 s[i]=s[j]dp[i][j]dp[i+1][j1]。(删除 [i+1,j1] 的最后一次搭上 i,j 即可)

同时任何时候,dp[i][j]dp[i][k]+dp[k+1][j]

括号涂色

给定一个匹配的括号序列,每一个括号可以不涂色或者涂两种颜色之一。每一对匹配的括号恰好有一个要涂色,相邻两个括号不能涂同色。求方案数。|s|700

dp[i][j][x][y] 表示 s[ij] 且两端颜色分别为 x,y 的方案数。

倒水

n 个杯子,第 i 个杯子容积 ai,初始有 bi 的水。你可以从一个杯子向另一个杯子倒水,但是 x 升水倒过去,会有 x2 升的水损失。

对于 p=1n,求出经过若干次倒水后,选出 p 个杯子,其中水总和最大是多少。

显然当我们选定之后,要尽量从没选的杯子往选了的杯子里倒水。

记原本总水量为 B,选了的杯子的集合为 S,选了的杯子的容积之和为 AS,选了的杯子目前有 BS 的水。

则最终的答案是 min(AS,BS+(BBs)/2)=min(AS,B/2+BS/2)

因此我们要让 BS 大。

dp[i][k][A] 表示前 i 个杯子,选出 k 个杯子,容积和为 A 的情况下,BS 最大是多少。

初值 dp[0][0][0]=0

dp[i][k][A]=max(dp[i1][k][A],dp[i1][k1][Aa[i]]+b[i]).

清除字符串

题意:每次可以删除一个字符块,求最小操作次数删完。

dp[i][j]:删光 s[ij] 的最小操作次数。

dp[i][i]=1,dp[i][i+1]=1+1×[s[i]s[j]]

dp[i][j]min(dp[i][k]+dp[k+1][j])

dp[i][j]dp[i+1][j]+1

dp[i][j]=min(dp[i+1][k1]+dp[k][j]),当 s[i]=s[k]。(把 s[i+1k1] 删掉,这样 s[i]s[k] 就相邻,可以视作一个字符)

异或

给定一棵树,每个点有 0/1 的点权。第 t 时刻,每个节点的点权变成第 t1 时刻它所有直接子节点的点权的异或和。

一个时刻 t 的权重定义为 t 时刻所有节点的点权和,一个对点权的安排 A 的权重 F(A) 定义为 t=0+ 时的权重之和。对于所有点权的安排 A,求出 F(A)

观察到,第 1 时刻点权是儿子们的异或和,第 2 时刻点权是孙子们的异或和 ……

定义 si:叶节点的 s=1,其余的 su=max{sson}+1

显然对于节点 u,只有 0su1 时刻,它的点权可能是 1。(过了 su,子孙深度就不够了)

假设某时刻 u 需要用到 cnt 个子孙来异或自身的贡献。下证:这些子孙的点权安排有 2cnt1 种可能使得 u 点权为 1

将一个使得 u 点权为 1 的01序列写成一个二进制数 xxx1 形成一一对应,且 xx1 恰好有一个有贡献。所以有一半的可能使得 u 点权为 1

既然这里有 2cnt1 种方法,算上其他的点随便安排,就有 2n1 种可能。

因此我们用 DP 求出 {s},然后计算 (2n1)si 即可。

补括号

给定一个长度 m 的括号序列,要求在两侧补上一些字符使得变成长度 n 的合法括号序列。求方案数。nm2000

预处理 dp[i][j]i 个字符,一共左括号比右括号多 j 个,且每一个前缀左括号都不比右括号数量少,的括号序列有多少个。

dp[i][j]=dp[i1][j+1]+dp[i1][j1],若 j=0=dp[i1][j+1]

然后枚举放在左侧的括号序列长度、左括号比右括号多的数量。
乘一下累加即可。

路径

给出 n 个点,m 条边的有向图。

求出一条任意起点、途中不能经过之前走过的点,并且包含 k 个点的权值最小的路径,如果不存在就输出 1

(u,v)经过点 x,当且仅当 min(u,v)<x<max(u,v)

1n,k80,0m2000,1ui,vin,1ci1000

走一条边,就会把能走的区间分开。考虑 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.valdp[i+1][l][edg_l.to][1],dp[i+1][edgl.to][r][0]dp[i][l][r][0/1]+edg_r.valdp[i+1][l][edg_r.to][1],dp[i+1][edgr.to][r][0]

答案:min0l<rn+1{dp[k][l][r][0/1]}

注意是开区间。

角斗场

n 个人,每个人有初始血量。每个回合,所有还活着的人会同时对场上其余人造成 1 伤害,然后血量 0 的死掉。

现在要你指派每个人的初始血量,要求在 [1,x] 之间。然后进行若干回合,使得最后无人生还。求方案数。

dp[i][j] 表示有 i 个人,最大血量为 j 的方案数。

i1j,随便安排人的血量即可。dp[i][j]=ji(j1)i

否则,枚举剩下的人的个数,并且此时剩余最大血量为 ji+1。所以 dp[i][j]=dp[k][ji+1]×(i1)ik×Cik

极差

给定序列 a,对其重排,使得 i=1n(maxj=1iajminj=1iaj) 最小。

观察发现,最后一个位置必然是最大值或者最小值。

于是对 ai 先升序排序,dp[i][j] 表示 aiai+j1 的答案。

dp[i][j]=min(dp[i+1][j],dp[i][j1])+ajai

二叉树

n 个结点,高度至少为 h 的二叉树的个数。(一个儿子的左右不同也算不一样)

dp[i][j]:$$ 个节点,高度不高于 j 的个数。

初值 dp[i][0]=0,dp[0][i]=1

递推 dp[i][j]=dp[k][j1]×dp[i1k][j1]

答案 dp[n][n]dp[n][h1]

蚂蚁

一个数轴的正半轴上有 n2e5 个传送门,第 i 个传送门在位置 xi。并给定他们的初始状态,ti=0 代表初始关闭,否则初始开启。

有一只蚂蚁在 0,以每秒 1 单位的速度向右走。如果踩到一个开启状态的传送门,就会被传送到 yi,yi<xi,且传送门关闭;如果踩到一个关闭状态的传送门,传送门开启。

问蚂蚁走到 xn+1 要多久。

观察:当蚂蚁位于 xi+1 时,传送门 1i 都处于开启状态。(自证)

dp[i] 表示当 1i 传送门都开启,且蚂蚁处在 xi 时,再走回 xi 需要多久。

dp[i]=(xiyi)+j=lftii1dp[j]。其中 lfti>yi 的位置小的传送门,也就是蚂蚁传送之后第一个碰到的传送门。

答案是 xn+1+iActivedp[i]Active 是所有初始开启的传送门集合。

字符串涂色

给定一个字符串,你要将每个位置涂色。如果两个相邻位置不同色,就可以交换这两个位置的字符。问最少涂多少种不同的颜色,可以使经过若干次交换后字符串升序排列。

升序排列:和逆序对有关。

即对于 i<j,si>sjclriclrj

dp[i] 表示 [i,n] 中以 i 开头的最长下降子序列长度,发现如果位置 idp[i] 染色,就可以做到。证明不难。

Star MST

dp[i][j] 表示加入了 i 个点,且这些点到 1 号点的最大距离为 j 的构图方案数。

叛徒

结论:

  1. 最坏情况肯定是初始叶子结点变成叛徒。否则下移更优。

  2. 最终叛变的是一颗子树。

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]=maxuson[i]{min(f[u],sz[u]/(sz[i]1))}

Black and White Tree

首先考虑链上的情况。发现 A 可以放在第二个位置,此时 B 必须放在第一个位置。然后问题规模 -2。

到树上,如果存在一个结点,有 2 个儿子是叶子,A 必胜;否则找到一个叶子结点 l 和他父亲 fl

先手下在 fl,后手必须下在 l,然后问题规模 -2。

(这只是略证)

Subtree

树形 DP + 前后缀积优化。

Book of Evil

树形 DP 求出 f1[i],f2[i]:子树内和子树外 i 距离最近是哪里。

三边

必然是一条直径 + 到直径最远的点。

先找出一条直径,再从直径上每个点向直径外搜索。

Neko Rules the Catniverse

给定 n,k,m,你需要求有多少个大小为 k 的序列 a 满足如下三个条件:

  1. 任意两个元素其权值不同。
  2. 对于任意 i 满足 1ik1ain
  3. 对于任意 i 满足 2ikaiai1+m
    答案对 109+7 取模。

1n1091kmin(n,12)1m4


正常想法是按照 a1an 的顺序 DP,但是发现这么做不了。究其原因是需要记录前面用过哪些数,会爆掉。

所以我们尝试按照从大到小的顺序 DP,相当于往一个序列不断插入更小的数。这样子可以保证插入顺序和数的大小顺序都有。

容易发现,保证每次插入后都合法的操作序列 与 原本的序列 可以一一对应。

一个简单的想法是 dp[i][j] 表示考虑 in 的所有数,已经插入的 j 个数的方案数。但是我们发现转移的时候无法判断 i1+m 的位置有哪些。注意到 m 的范围很小,可以状压。

dp[i][j][S] 表示考虑了 in 的所有数,插入了 j 个数,同时如果 bitS,则 i+bit 在序列里,的方案数。
然后可以 O(1) 转移。

但是 i 这一维是 109 的,需要优化。我们上一个矩阵快速幂优化就行了。

Petya and Arrays

按照数组顺序 DP 难做。

考虑另一个数组 s0=0,s1snaip 意义下的前缀和。因为每个数都是 1p 的,所以 sa 一一对应。

而限制转移到 s 上,就可以总结为:

  1. si+1siAmodp

  2. s0sn 各不相同。

观察到如果构图 i(i+A)modp,最终会形成若干个环。最终要求就相当于在环上不能有相邻的数。

于是可以改换枚举顺序:按照环枚举。基础想法 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

  1. (x,y) 是终点,胜率为 1

  2. (x,y) 是已知的危险颜色或者障碍物,胜率为 0

  3. (x,y) 是已知的安全颜色,胜率为 dp[x][y][S][d]

  4. (x,y) 是未知颜色。胜率为 (1trap[clr])dp[x][y][S+clr][d]+[d=7]trap[clr]dp[x][y][S][clr]

然而还有一个问题:DP 顺序。这其实用记忆化搜索就行了,从终点开始。

寿司晚宴

两个人各从 {2n} 选一个子集,要求从这两个子集里各任意选一个数,都必须是互质的。允许空集。求方案数。n500

容易想到要记录两个子集各自包含哪些质因数。对于 n30 的可以直接这样做状压 DP,但是 n500 的质数太多了。

思考发现,其实对于 23 的质数,不可能同时包含两个。把质数分成小质数(19)和大质数(23),每个数至多包含一个大质数。所以可以把所有数按照包含的大质数分类,没有大质数的作特殊一类。

发现除了特殊一类,每一类内的数都不可能存在两个数分给两个人(所有数要么不选,要么只会属于一个人)。那它们对答案的贡献可以直接搞个快速幂求出来,我们还是只需要处理特殊一类。而这一类不包含大质数,沿用上面状压 DP 的方法即可。

Mx 的组合数:UOJ86

看到 1030 肯定不能跟正常枚举有关系。考虑把数改成 p 进制的进行数位 DP。

x=(xtx1)p,n=(ntn1)p,r=(rtr1)p。因为 n,r 实在是太大了,面对这种模数是小质数而 x,n 很大的,尝试使用卢卡斯定理。

(nx)=(n/px/p)(nmodpxmodp)modp。发现在 p 进制下相当于去掉了末位。于是发现 (nx)=(nixi)modp

数位 DP 都是从高到低的。dp[i][j][f] 表示填了 xtxia=it(naxa)=jf=0 表示目前 x 填的数还和 r 相等,f=1 表示 x 已经 <r

但是这个复杂度是 O(p2logpr) 的,会超时。这个优化就非常神奇了。

p 的原根,更换状态描述 dp[i][j][f]:其他都相同,但是 a=it(naxa)=gj

这个时候我们发现如果从 j 出发,枚举下一位贡献了 gk,就会转移到 dp[i+1][j+k][?]。从 j,k 一起转移到 j+k,这让我们想到卷积。而确实我们如果把 dp[i][][f] 视作多项式,就可以用一次卷积得到 dp[i+1][][f]
还有一个要注意的就是 dp[i][0][f] 无法 j+k 求出,但是我们可以通过总方案数减去 dp[i][1p1][f] 得到。

因此复杂度降低为 O(plogplogpr)

poj1090:Chain

观察发现,操作序列构成格雷码。而且每个操作序列都会先走到 000

反过来求 000 走到一个 (gkg1)2。考虑格雷码一个常见的构造方式:一位的格雷码排序为 0,1,记 n 位格雷码排序为 f(n),则 n+1 位的一种格雷码可以表示为 0+f(n),1+rev(f(n))

发现按照这种格雷码,就是最优操作序列。问题变为给定一个二进制数求在格雷码中的排序。

这里有一个数学结论:第 n 个格雷码 =n[n2]。(格雷码的显示构造)

推论:把 n 也写成 k 为二进制数。则有

gk=nkgk1=nk1nkgk2=nk2nk1g1=n2n1

反推可得 ni=j=ikgj

TopC13457:BoardFolding

给定一张方格表,每个格子有不同的颜色。可以沿着格子线横着折或者竖着折,要求必须是小的那边折到大的那边,而且折叠对应的位置必须颜色相同。问任意次操作之后可以得到哪些子矩阵?

一个基础的想法是求一个 f[u][d][l][r],表示上下左右的边分别是 u,d,l,r 的子矩阵是否可以被折叠出来。不过是 O(n4) 的。

这里观察到一个性质:横着折和竖着折之间是独立的,也就是判断 (u,d,l,r) 是否可行,只需要判断 (u,d) 是否可行和 (l,r) 是否可行。
那么只用求 f[u][d]F[l][r],于是降到 O(n2)

到这里可以通过原题,但是我们还能继续优化!
观察到 (u,d) 可行 (1,d) 可行和 (u,n) 可行。于是再次降成四次状态定义是一维的 DP。

具体考虑怎么 DP,这里只描述 l[x]:是否能保留 1x 列。
首先初值 l[m]=1;其次考虑 l[i](i<m),设以 i,i+1 之间为对称轴的最大回文半径是 r,则 l[i]=1 当且仅当 l[i+1]l[i+r] 有一个 1
求回文半径可以用 manacher 预处理。
如果直接 DP,还是 O(n2) 的;但是显然能看出来这里有一个优化。维护一个变量 pos,表示 l[i+1]l[m] 最靠左的 l[]=1 的位置在哪里。这样只用判断 i+rpos 即可,如果成立,l[i]=1,同时令 pos=i

GYM100286C:Clock

就是给定一个很多根针的钟和之间的联动关系,求从 S 时刻调到 T 时刻要多久。(允许逆时针调)

首先发现可以把问题转化为从 0 时刻调到 TS 时刻,就是把 S 视作起始时间。

把针 i 记作 pipidi1 格(1di1 圈),每走一格 pi1 就走一圈。
所以我们说 pi 现在指向 x 点,不如把它转成指向 [xdi1] 格。注意这里的取整,因为被取整掉的部分是 p1pi1 的事情。

于是给每个 pi 求一个 bi=[xdi1]。目的就是让 pi 最终指向 bi

因为从高位到低位没有影响,因此如果我们从高到低 DP 就是无后效性。粗略的想法是 dp[i] 表示调完 pnpi 使得它们都指向各自的 b

但这是不行的,因为 i11 的针会对 pnpi 产生影响,最优解很有可能 pi 没有指向 bi,而是在 1i1 的针调的时候顺路调了。

好像卡住了?
仔细思考,pi 最终必须指向 bi。而如果 dp[i] 结束的状态里,pi 指向 bi1p1pi1 要让 pi 动必须转一圈,肯定不如 pi 动一格;pi 指向 bi+2 也是同理。所以唯一另外的可能就是 pi 指向 bi+1,然后 p1pi1 变小,让 pi 退位。

于是得出结论:当 pnpi 调好的时候,pi=bi/bi+1

dp[i][0/1] 表示调好 pnpi,且此时 pi=bi/bi+1 的最小移动量。

dp[i][0]=min(dp[i+1][0]+biai,dp[i+1][1]+(di1bi)ai):如果 pi+1 指向 bi+1pi 就从 0 正着调,否则反着调。

dp[i][1]=min(dp[i+1][0]+(bi+1)ai,dp[i+1][1]+(di1(bi+1))ai):同理。

修正表达式

题意:给定三个数 1a,b,c109,一次操作为选一个数进行插入或删除或修改 某个数位。要求任何时刻 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 表示目前已经填好的数位 cab 是多少,A,B,C 分别记录当前指向 a,b,c 的哪一个数位,S 是一个 23 的状态用于描述 a,b,c 目前是否是前导零,Lim 表示接下来还允许 Lim 次插入(为了保证不会一直选择插入而死循环)。

pt3A,B,C9S8,而 D10:如果 D >10,随着数位的后移,D 这个差距后面是无法挽回的。Lim 显然 30

转移时枚举 pt 这一位的操作/要加入或修改成什么数字,记得 D 在转移时要 ×10

CF710E: Generate a String

初始一个 0,每次可以加减 1(给定代价 x)或者 ×2(给定代价 y),求变成 n 的最小代价。

一个 O(nlogn) 的想法是最短路,ii1,i+1,2i,点的个数开到 2n。但是这跑不过原题。

注意到 1 操作不会连续进行两次,因为 1 前面有 ×2(不可能 +11),不如在 ×2 之前 1,效果是一样的。

所以等价于三种操作:ii+1,2i,2i1。直接按照这个 DP 即可。

AGC012E: Camel and Osase

给定 n 个绿洲,第 i 个绿洲的坐标为 xi ,保证 109x1<x2...<xn109
现在有一个人在沙漠中进行旅行,他初始的背包的水容积为 V 升,同时他初始拥有 V 升水 ,每次到达一个绿洲时,他拥有的水的量将自动重置为容积上限(可以使用多次)。他现在可以选择两个操作来进行旅行:
1. 走路,行走距离为 d 时,需要消耗 d 升水。清注意,任意时刻你拥有的水的数量不能为负数。
2. 跳跃,令 v 为你当前拥有的水量,若 v>0,则你可以跳跃至任意一个绿洲,然后重置容积上界和所拥有的水量为 v/2 (向下取整)。
对于每一个 i 满足 1in ,你需要求解,当你在第 i 个绿洲作为起点时,你能否依次遍历其他所有绿洲。如果可以,输出 Possible,否则输出 Impossible


容易观察到每次跳跃后水量折半,跳跃次数不会太多,一共 logV<20 次。
因此可以预处理出 l[i][j],r[i][j],表示绿洲 iV2j 的水,最左(右)能去到哪个绿洲。

看作 logV 层,第 i 层对应初始水量 V/2i,注意要给 0 水分保留一层。每一层有 n 条线段([l[i][j],r[i][j]])。对于 i 的答案,就是固定第 0 层选择某条线段,判断从 1logV 层能不能选出线段覆盖 1n

用状压 DP 实现,dpl[S] 表示从 S 中的层选择线段,左端能连续覆盖到哪里,比如选出线段 [1,4],[3,5],[7,8] 就是连续覆盖到 5dpr[S] 就是右端连续覆盖到哪里。

直接暴力判断答案,就是枚举 i1n,然后枚举 S(注意不包含第 0 位了),判断是否存在 dpl[S]l[0][i]1dpr[US]r[0][i]+1

但是这样是 O(n2) 的。我们发现如果给第 0 层的线段去重的话,留下来的线段应当不超过 logV 条:如果超过了 logV 条,说明有 >logV 个绿洲,即使用最多的水量也无法互相到达,所以这种情况答案一定全都是 Impossible。

于是留下 logV 条第 0 层的线段,在枚举第 0 层线段的复杂度就变成 O(logV),总复杂度 O(n+VlogV)

TopC10664: RowGame

题意:给定长度 n 的数组,从 1 出发,初始 0 分。每次选择一个位置 x 走到那里,收获分数为 x 到原本位置的数和,要求每次走之后分数都非负。走 k 步,求最大分数。n50,k4×108

容易发现,最理想情况下我们肯定是选择一个和很大的区间来回走。进而我们不难猜出路径的形式:找到一个区间,反复刷分直到足够走到下一个区间,然后去下一个区间继续刷分 …… 直到不够次数向后走了,就停下来刷分。

严格来说,假设最优路径会选择 [j1,i1][jx,ix] 这些区间,则有:

  1. sum[jt,it]<sum[jt+1,it+1]
    这显然,不然没有必要去新区间。
    由此推导出每个区间的和都是正的,因为走第一步要求非负,所以第一个区间是正的,后面的区间都大于前面的。

  2. it+1itjt 为使得 sum[a,it] 最大的 a
    后半部分很显然。记使得 sum[a,x] 最大的 aL(x)

    前半部分,反证法,则 it+1<it
    jt+1<jt,因为 jt=L(it),所以 sum[jt+1,jt1]<0,则 L(it+1)jt+1,矛盾。
    jt<jt+1,同理得到 sum[jt,jt+11]<0,有 L(it)jt,矛盾。
    jt=jt+1,因为区间分数递增,所以 sum[it+1+1,it]<0,那没必要去 [jt,it],直接去 [jt+1,it+1] 即可。

  3. it+1>it 时,jt+1>it
    反证,当 jt+1[jt+1,it] 时,因为 jt+1=L(it+1),所以 sum[jt,jt+11]<0,则 L(it)jt,矛盾。
    jt+1=jt,因为分数递增,所以 sum[it+1,it+1]>0,也没必要去 [jt,it],因为不刷分也可以直接走。
    jt+1<jt,因为 L(it)=jt,所以 sum[jt+1,jt1]<0L(it+1)jt+1,矛盾。

  4. 最优解总是用最少步数走到新区间。
    如果不用最少步数,说明前面浪费了一步刷分。但前面刷分不如刷新区间的分,新区间的分一定比前面的更大。

dp[i] 包含两个元素:stepscore,表示走到第 i 个位置的最小步数和在最小步数下的最大分数。

CF111C:Petya and Spiders

观察发现,蜘蛛的最优行动肯定是若干个 "十",每个十字覆盖区域的蜘蛛都集中到中心点。
所以只要求出最少用多少个十字覆盖整个网格图,n×mans 就是答案。

因为 n×m40,所以较短边 6,可以状压。dp[i][s1][s2] 表示前 i 行,第 i1 行覆盖状态为 s1,第 i 行覆盖状态为 s2,第 1i2 行的格子全部覆盖的方案数。

初值设定,枚举第 1 行哪里放了十字中心,以 dp[2][][] 作为 DP 起点。
转移时枚举第 i 行哪里放十字。要保证 s1 没覆盖的部分能用 i 行的十字覆盖。
答案为 mindp[n+1][111][?]

CF678E:Another Sith Tournament

正向 DP 不好做,考虑 dp[i][S] 表示余下 S 的骑士,i 是擂主,最后只剩下 1 的概率。初始 dp[1][1]=1
答案就是 maxdp[1n][{1n}]

方块消除

题意:给定一行方块,带颜色。每次选择一个颜色段消除,剩下的方块再拼起来。一次消除如果消了 x 个,会得到 x2 分,求最大分数。初始 n 个块,每个块有 ai 个格子。

最初想法是 dp[l][r] 表示消除 lr 的颜色块的最大分数,但是不行,因为有可能最优方案是先消除中间的某一段,然后让剩下的拼起来。
拼起来的目的是让一段后面增加和它同颜色的方块。考虑升维 dp[l][r][k] 表示消除 lr 的颜色块,同时在后面接了 k 个和 r 同色的格子的最大分数。

转移方程怎么写?对于 dp[l][r][k],考虑段 r 是怎么消除的。

  1. 不参与和其他段的合并,直接自己消除:从 dp[l][r1][0]+(a[r]+k)2 转移来。

  2. 参与和其他段的合并,枚举是和哪一个段 t 进行第一次合并:从 dp[t+1][r1][0]+dp[l][t][a[r]+k] 转移来,这种情况要求 clr[t]=clr[j]

因此 dp[l][r][k]=max(dp[l][r1][0]+(a[r]+k)2,dp[t+1][r1][0]+dp[l][t][a[r]+k])
还有一个问题:k 的范围要枚举到多少呢?对于 [l,r] 段,最多在后面接的数量,就是 [r+1,n] 中与 r 同色的方块数量,预处理出这个即可。

CF1392D:Okmar and Bed Wars

观察发现,若 i 的指向不合法,当且仅当 i1,i,i+1 都是同方向的。
所以合法 不存在连续三个人指向相同。

先考虑在链上的情况。对于链,容易想到把每一段相同指向的分开。设一段的长度是 n,贪心得到需要 n/3 次改变,所以把每一段的 n/3 加起来即可。

在环上的大部分情况,按照链的情况也是对的(每一段相同的长度除以三求和),但是我们发现当整个环的指向都相同的时候,需要 (n+2)/3 次改变。

如何处理环上首尾同向但是无法统计的情况?把 s 变成 ss,找到第一个相邻不同的位置 ii 作为起点找 ssn 长度子串即可。


DP 做法:
最终合法状态一定是由 RL,RRL,RLL,RRLL 组成。于是可以直接 DP。
但是在环上,旋转后也合法。因为 DP 第一次决策最多涉及前四个位置,把 s 旋转四次分别做 DP 然后取最小值即可。

CF1334F:Strange Function 题解

烟火表演题解

ABC221G:Jumping Sequences 题解

重建计划 题解

HDU5293:Tree Chain Problem 题解

CF868E:Yet another Minimization Problem 题解

posted @   FLY_lai  阅读(34)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示