DP从入门到出门

[TOC]

早有打算做点dpdp题,今天恰好看到gyh写了一个DP从入土到入门,那就硬抄呗(狗头

线性DP

顾名思义,就是在线性状态上进行递推
常见在序列上对区间操作的问题,如区间合并

P4910 帕秋莉的手环

P4910 帕秋莉的手环

给定一个有nn个位置的环,每个位置可以填入1/01/0,要求不能有相邻的11

多组数据T10,n1018T\leq 10,n\leq 10^{18}

  • 20pts20pts

暴力枚举

  • 60pts60pts

f[i][0/1]f[i][0/1]表示第ii个位置是0/10/1的方案数

{f[i][1]=f[i1][0]f[i][0]=f[i1][1]+f[i1][0]\begin{array}{l} \left\{\begin{matrix} f[i][1]=f[i-1][0]\\f[i][0]=f[i-1][1]+f[i-1][0]\end{matrix}\right.\end{array}

复杂度为O(Tn)O(Tn),期望得分60pts60pts

  • 100pts100pts

考虑矩阵优化

[fi1,0fi1,1]×[1110]=[fi,0fi,1]\begin{bmatrix}f_{i-1,0}&f_{i-1,1}\end{bmatrix} \times \begin{bmatrix}1&1\\1&0\end{bmatrix} = \begin{bmatrix}f_{i,0}&f_{i,1}\end{bmatrix}

复杂度为O(Tlogn)O(Tlogn)期望得分100pts100pts

代码见P4910 帕秋莉的手环

P4933 大师

P4933 大师

给定nn个建筑,第ii高度为h[i]h[i],求拆掉某些建筑后剩下的是等差数列的方案数(对998244353998244353取模)

n1000,hmax20000n\le 1000,h_{max}\le 20000

  • 30pts30pts

枚举每个建筑拆不拆

复杂度为O(2nn)O(2^{n}*n),期望得分3030

  • 60pts60pts

枚举等差数列的公差,然后每次扫一下整个序列,看看能否满足,hmax2000h_{max}\le2000

上面的是错误的算法,我傻了

但是我们注意到值域hmax2000h_{max}\le 2000,其实是很小的

大胆猜测枚举公差的方向肯定没错,淦

f[i][j]f[i][j]表示以ii结尾,公差为jj的等差数列有多少个

每次枚举一个小于ii的位置kk,让kk的高度满足h[k]=h[i]jh[k]=h[i]-j,然后用f[k][j]f[k][j]更新f[i][j]f[i][j]

复杂度为O(n2hmax)O(n^2*h_{max}),期望得分6060

  • 100pts100pts

考虑优化60pts60pts

等差数列任意相邻两个数的公差是一定的

我们没必要去枚举公差,直接后面的减去前的就得出来公差了,qwqqwq

f[i][hihk]=k<if[k][h[i]h[k]]+1f[i][h_i-h_k]=\sum\limits_{k<i}f[k][h[i]-h[k]]+1

复杂度为O(n2)O(n^2),期望得分100100

代码见P4933 大师

P3847 调整队形

P3847 调整队形

给定长为nn的颜色序列,颜色为11nn,问最少经过多少次操作能让颜色序列左右对称

操作有添加,删除,插入,替换颜色

n3000n \le 3000

仔细分析题目题解可以发现:

  • 在队伍中任两个人中间插入一个人(衣服颜色依要求而定)
  • 剔掉一个人

这两个操作是没必要的,因为插入一个人的状态一定包含在加入一个人的状态里面,同理剔除一人和加入一个人等效

所有我们就只需要处理加入一个人和换颜色即可

f[i][j]f[i][j]表示iijj对称需要的最小操作次数

  • 加入一个人

f[i][j]=min(min(f[i][j1]+1,f[i+1][j]+1),f[i][j])f[i][j]=\min(\min(f[i][j-1]+1,f[i+1][j]+1),f[i][j])

  • 换颜色

f[i][j]=min(f[i+1][j1],f[i][j])f[i][j]=\min(f[i+1][j-1],f[i][j])

代码见P3847 调整队形

P4170 涂色

P4170 涂色

给定一个长为nn的序列,每次可以把一段涂成某种颜色,后涂的会覆盖先涂的,问最少涂几次能得到目标状态

n50n\le50

和上面拿到题目类似
f[i][j]f[i][j]表示iijj最少的涂色数量
分情况讨论
i=ji=j时,显然有f[i][j]=1f[i][j]=1
iji\neq j

  • 如果a[i]=a[j]a[i] = a[j],就继承一下上一次涂的色,f[i][j]=min(f[i+1][j],f[i][j1])f[i][j]=\min(f[i+1][j],f[i][j-1])
  • 如果a[i]a[j]a[i]\neq a[j],就把颜色看成两段,枚举分割点kkf[i][j]=min(f[i][k]+f[k+1][j])f[i][j]=\min(f[i][k]+f[k+1][j])

代码见P4170 涂色

P3146 248 G

P3146 248 G

给定一个1n1*n的地图,在里面玩20482048,每次可以让相邻的两个相同的数xx合并成x+1x+1,问最大能合出多少。

1x401\leq x \leq40

这种合并操作基本就是区间DPDP(狗头

f[i][j]f[i][j]表示iijj合并的最大值(区间DPDP经典状态)

显然转移方程就是f[i][j]=max(f[i][j],f[i][k]+1),k[l,r),f[i][k]=f[k+1][j]f[i][j]=\max(f[i][j],f[i][k]+1),k\in[l,r),f[i][k]=f[k+1][j]

这样就可以水过本题了,当时其实这样是错误的

HackHack数据:

Input:

8 2 1 1 2 4 2 3 4

Output:

4

如果f[i][k]=f[k+1][j]=0f[i][k]=f[k+1][j]=0的话,按照上述转移方程得到的f[i][j]=1f[i][j]=1,就错了

f[i][j]=max(f[i][j],f[i][k]+1),k[l,r),f[i][k]=f[k+1][j]>0f[i][j]=\max(f[i][j],f[i][k]+1),k\in[l,r),f[i][k]=f[k+1][j]>0

代码见P3146 248 G

P5774 病毒感染

P5774 病毒感染

nn个小镇,每个小镇aia_i个人感染了病毒,JYYJYY每到一个村庄可以选择救治该村所有的人(这一天没有人死亡),或者去下一个村庄,当他已经跳过一个村庄后再向该村庄走时(此处不明白请仔细看题目中绝对值的关系那个地方),必须来救治该村庄

ii个村假设有aia_i个人感染,那么第二天这aia_i个人会死亡且会新感染aia_i个人,求最小死亡数

1n3000,1ai1091\le n\le3000,1\le a_i\le10^9

这题真的是普及+/提高吗?

  • 50pts50pts

定义f[i]f[i]为治愈前ii个村庄的最小死亡数

对于每个村庄来说JYYJYY可以选择治疗还是不治疗,重点在于怎么处理回去这的问题

由题目中ki<kj|k-i|<|k-j|可得只要想从jj回去治疗kk,那就必须把之前的全都治了

所以我们只需要枚举kk即可实现转移

复杂度O(n3)O(n^3),期望得分:5050

  • 100pts100pts

上面的做法时间主要浪费在计算略过村庄再回来的死亡人数上,我们可以考虑预处理这一部分

定义g[i][j]g[i][j]表示从iijj在回到ii的最少死亡人数

但是我不知道g[i][j]g[i][j]应该怎么转移(看不懂题解),先挖个坑,以后回来写完

回来填坑了

枚举ii为起点,jj为长度,sum[i]=k=1ia[k]sum[i]=\sum\limits_{k=1}^{i}a[k]

g[j][i+j]=g[j+1][i+j]+min((sum[i+j]sum[j])2,a[j]i3+sum[i+j]sum[j])g[j][i+j]=g[j+1][i+j]+\min((sum[i+j]-sum[j])*2,a[j]*i*3+sum[i+j]-sum[j])

则设f[i]f[i]为前ii个村庄消灭疫情最小死亡人数

f[i]=min(f[j]+g[j+1][i]+(4i4j2)(sum[n]sum[i]))f[i]=\min(f[j]+g[j+1][i]+(4*i-4*j-2)*(sum[n]-sum[i]))

淦,这转移真恶心,如果看不懂请点这里

代码见P5774 病毒感染

P2501 [HAOI2006]数字序列

P2501 [HAOI2006]数字序列

给定一个nn个数的序列,问最少改变多少个数能让它变成一个单调严格上升的序列,且让每个数改变的绝对值之和最小,输出个数和最小值

1n3.5104,1ai1051\le n\le3.5*10^4,1\le a_i\le 10^5

  • Problem 1Problem\ 1

题目之后,淦,这什么东西啊

我陷入了满足的数应该被改变的思考中......嗯,想不出来

正着想不出来可以反着想,考虑满足什么条件的数应该被保留

如果a[i]a[i]a[j]a[j]之间的数本身合法或者能被改成合法的那么它们就是应该被保留的

容易得出满足a[j]a[i]jia[j]-a[i]\ge j-i

a[j]ja[i]ia[j]-j\ge a[i]-i

显然,可以令b[i]=a[i]ib[i]=a[i]-i,然后求bb的最大不下降子序列(因为条件是\ge)的长度lenlennlenn-len即为第一问答案

  • Problem 2Problem\ 2

我们继续在bb上考虑,对于一对被保留的点b[i]b[i]b[j]b[j],他们之间的数一定都是不合法

现在的问题就在于如何把这些数都改变才能使改变量最小

一个结论

设分界点为kk,则对于左边b[l]b[i],l[i,k]b[l] \to b[i],l\in[i,k]对于右边b[r]b[j],r[k+1,j]b[r]\to b[j],r\in [k+1,j]

证明的话就不写了,我也不会

g[i]g[i]表示[1,i][1,i]的在改变次数最小时最小改变量之和

g[i]=min(g[j]+w(j+1,i))g[i]=\min(g[j]+w(j+1,i))

根据上面的结论可以通过前缀和和后缀和来优化求w(j+1,i)w(j+1,i)的过程

复杂度为O(n2+nlogn)=O(n2)O(n^2+nlogn)=O(n^2),期望得分:100100

代码见P2501 [HAOI2006]数字序列

P5301 [GXOI/GZOI2019]宝牌一大堆

P5301 [GXOI/GZOI2019]宝牌一大堆

不简化了,太长了

做的第一道NOI/NOI+/CTSCDPDP,做这个题要时刻记得各种牌是怎么组成的,qwqqwq

雀魂大毒瘤,麻将已无爱

  • 七对子

直接贪心选择权值最高的七个即可

  • 国士无双

暴力枚举,O(132)O(13^2)

  • 34+23*4+2

f[i][j][k][a][b][c]f[i][j][k][a][b][c]表示枚举到第ii张牌,已经有了jj张面子,有有kk对雀头也可,第ii种牌用了aa张,第i+1i+1种用了bb张,第i+3i+3种用了cc张的最大分数,或者k=0/1k=0/1

然后枚举转移即可

  • 性质11

经过艰难的读题后可以发现杠子是不需要考虑的,因为杠子的价值C44=1C_4^{4}=1就算是宝牌也没有刻子的高C43=4C_4^{3}=4

  • 性质22

三个刻顺子是没有三个刻子更优的,所有对于l,ml,m只需要枚举到22就行了

  • 性质33

ff值是00的话说明不合法直接退出即可

  • 转移方程

f[i][j][1][k+2][l][m]=max(f[i][j][1][k+2][l][m],f[i][j][0][k][l][m]Ca[i]kCa[i]k+2dora[i]2)f[i][j][1][k+2][l][m]=max(f[i][j][1][k+2][l][m],\frac{f[i][j][0][k][l][m]}{C_{a[i]}^k}*C_{a[i]}^{k+2}*dora[i]^2)

f[i][j+1][o][k+3][l][m]=max(f[i][j+1][o][k+3][l][m],f[i][j][o][k][l][m]Ca[i]kCa[i]k+3dora[i]3)f[i][j+1][o][k+3][l][m]=max(f[i][j+1][o][k+3][l][m],\frac{f[i][j][o][k][l][m]}{C_{a[i]}^k}*C_{a[i]}^{k+3}*dora[i]^3)

f[i][j+1][o][k+1][l+1][m+1]=max(f[i][j+1][o][k+1][l+1][m+1],f[i][j][o][k][l][m]ca[i]kca[i]k+1dora[i]ca[i+1]lca[i+1]l+1dora[i+1]ca[i+2]mca[i+2]m+1dora[i+2])f[i][j+1][o][k+1][l+1][m+1]=\max(f[i][j+1][o][k+1][l+1][m+1],\frac{f[i][j][o][k][l][m]}{c_{a[i]}^k}*c_{a[i]}^{k+1}*\frac{dora[i]}{c_{a[i+1]}^l}*c_{a[i+1]}^{l+1}*\frac{dora[i+1]}{c_{a[i+2]}^m}*c_{a[i+2]}^{m+1}*dora[i+2])

  • 注意

注意在转移之前判断合法不合法(剩的牌够不够),开long long
为什么要开O2O2才能过?我吧我忘记判断不合法状态了,qwqqwq

复杂度为O(344422)=O(2176)O(34*4*4*2*2)=O(2176),期望得分:100100

代码见P5301 [GXOI/GZOI2019]宝牌一大堆

状压 DP

顾名思义,就是通过二进制压缩,将状态转化为一个数,用这个数作为动态规划的状态进行 DPDP

常见于某些数据范围较小的题目,如n32n\le32,n64n\le 64

P3622 [APIO2007]动物园

P3622 [APIO2007]动物

给定一个有nn个动物的环,你可以移除某些动物,有cc个小朋友,每个小朋友能看到55个动物,每个小朋友有喜欢的和不喜欢的动物

定义小朋友高兴为

  • 至少看到他喜欢的一个动物
  • 至少有一个他不喜欢的动物被移除

问最多有多少小朋友高兴

10n104,1c510510\le n \le10^4,1\le c\le5*10^5

每个人只能看到55个动物而且每种动物只有0/10/1两种状态,这提示我们使用状压来做

不妨先考虑环的问题,显然可以每55个划分状态

f[i][s]f[i][s]表示从到ii[i,i+5)[i,i+5)这几个动物的状态为ss时最多能使多少小朋友开心

f[i][s]=max(f[i1][(s&15)<<1],f[i1][(s&15)<<11])+sum[i][s]f[i][s]=\max(f[i-1][(s\&15)<<1],f[i-1][(s\&15)<<1|1])+sum[i][s]

sum[i][s]sum[i][s]表示状态是ss,视野为[i,i+5)[i,i+5)的小朋友高兴的人数

  • 对环的处理

环上第nn个动物时的状态ss的后四位必须与第11个动物的前四位的状态相同

则只需强制开始状态和结束状态一样才能更新,可以通过外层在加一个循环来表示初始状态来实现,具体见代码

复杂度为O(322×n)O(32^2\times n),期望得分:100100

代码见P3622 [APIO2007]动物园

P2150 [NOI2015]寿司晚宴

P2150 [NOI2015]寿司晚宴

给定n1n-1中不同的寿司,第ii种寿司的美味度为i+1i+1,小GG和小WW从中挑选一些来品尝,要求他们选得寿司中美味度必须都互质,问有多少种方案(对pp取模)

2n500,0<p1092\le n \le500,0<p\le10^9

  • 30pts30pts

n30n\le 30一共有1010个质数,我们可以状压一下两人已经选的集合

显然对于一个寿司,如果它的质因子都不在在小GG的集合里,那么它可以放入小WW的集合里面

f[i][s1][s2]f[i][s1][s2]表示到第ii个寿司,小GG选择的质因子集合是s1s1,小WW选的是s2s2

$\left{\begin{matrix} f[i][s1|p_i][s2]+=f[i-1][s1][s2] & p_i&s2=0 \ f[i][s1][s2|p_i]+=f[i-1][s1][s2] & p_i&s1=0 \end{matrix}\right. $

pip_i为第ii个寿司的质因数集合

上述转移方程竟然可以通过滚动数组来优化(奇怪的知识增加了

复杂度O(210×n)O(2^{10}\times n),期望得分3030

  • 40pts40pts

没想到有什么只能过40pts40pts的,如果有只能过40pts40pts的麻烦告诉蒟蒻一下

  • 70pts70pts

同样不知道,qwqqwq

  • 100pts100pts

对于11个数nn它至多有11>n>\sqrt{n}的质因子

根据上面的性质,我们没必要把所有质因子都状压,只需要将质因子进行分类

一类是”小因子“<n<\sqrt{n},一类是大“因子”>n> \sqrt{n}

a[i].firsta[i].first表示大因子集合,用a[i].seconda[i].second表示小因子集合

所有大因子相同的数显然只能放在一个集合中,所以按照大因子的大小排序,这样就可以把所有大因子相同的数放在一起算

在计算一段相同的大因子的数时,我们可以把f[s1][s2]f[s1][s2]拆成g1[s1][s2]g1[s1][s2]g2[s1][s2]g2[s1][s2]分别表示这段数都放在集合11和集合22里的答案,g1,g2g1,g2仍按照30pts30pts的方程进行转移即可

f[s1][s2]=g1[s1][s2]+g2[s1][s2]f[s1][s2]f[s1][s2]=g1[s1][s2]+g2[s1][s2]-f[s1][s2](f[s1][s2]f[s1][s2]加了两遍)

最终答案ans=s1s2f[s1][s2]ans=\sum\limits_{s1}\sum\limits_{s2}f[s1][s2]

注意s1=0s1=0s2=0s2=0的情况

代码见P2150 [NOI2015]寿司晚宴

树形 DP

顾名思义就是在树上进行的DPDP ,在设计状态是通常考虑子树如何才能最优,然后考虑怎么用子树更新根节点

常见于在树上统计子树和,在树上选一些点满足价值最大,花费最少的费用覆盖所有点,树上统计方案数问题等问题中

常见的规律:

一般来说树形dpdp在设状态转移方程时都可以用f[i][]f[i][]表示ii这颗子树怎么怎么样的最优解,实现时一般都是用子树更新父亲(即从下向上更新),那么首先应该考虑的是一个一个子树的更新父亲还是把所有子树都算完了在更新父亲?这就要因题而异了,一般来说有两种情况:

  1. 需要把所有子树的信息都掌握之后再更新子树的就需要把所有子树都算完了在更新父亲。
  2. 而像树上背包这样的问题就需要一个一个的更新,每次都用一个子树更新已经更新完的子树+父亲,最后就可以将这一部分的子树更新完了,再继续往上更新,最后根节点就是答案。

其实上面的两种情况可以总结成一种情况就是一个个子树更新父亲,一般来说第一种情况应用更多,也能解决第二情况的问题,只不过如果符合第二种情况的时候用第二种可以速度更快一点,毕竟你省了一遍循环嘛。

原文链接:https://blog.csdn.net/dcx2001/article/details/78269908

CF767C Garland

CF767C Garland

把一棵nn个节点树切成33部分,使得每一部分的点权和相等。

n106n \le 10^6

设所有的节点的权值和为sumsumf[u]f[u]表示以uu为根的子树的权值和

f[u]=a[u]+vsonuf[v]f[u]=a[u]+\sum\limits_{v\in son_u}f[v]

如果uu有一个儿子vv使得f[v]=sum3f[v]=\frac{sum}{3}则,我们将vv切开,并且将f[u]=f[v]f[u]-=f[v]

最后统计是否有两个切点即可

代码见CF767C Garland

P3942 将军令

P3942 将军令

给定nn个节点的树,节点上可以放小队,每个小队可以控制距离该点不超过kk的所有点,问最少放多少小队能全部控制nn个节点

n105,k20n\le10^5,k\le 20

  • 5pts5pts

直接输出nn

  • 45ptsk=145pts,k=1

P2279 [HNOI2003]消防局的设立很类似,设f[i][0/1/2]f[i][0/1/2]表示ii这个点被儿子00,自己11,被父亲22控制

$f[u][0]=sum( \min (f[v][0],f[v][1]))- \min(0,\max(f[v][1]-f[v][0]) ) $

f[u][1]=sum(min(f[v]))f[u][1]=sum(\min(f[v]))

f[u][2]=sum(min(f[v][0],f[v][1]))f[u][2]=sum(\min(f[v][0],f[v][1]))

  • 75ptsk=275pts,k=2

和上面的一样,只要在第二维再加两个状态,孙子和儿子全部控制但自己没被控制33和孙子全被控制但是自己和儿子不全被控制44

  • 90pts90pts

依旧再加状态,不在赘述

  • 100pts100pts
如果崩了,请联系我
  • 贪心100pts100pts

这种题目有个结论,一定是在该点的kk级祖先(若没有则是根节点)

那么从下往上更新答案即可
代码见P3942 将军令

P3523 [POI2011]DYN-Dynamite

P3523 [POI2011]DYN-Dynamite

给一棵树,书上有一些关键节点,要求你选m个点,使得关键节点到这些点中距离的最小值的最大值最小,求这个值

看完题目就感觉到浓浓的二分答案的味道,显然外层有个二分答案

在树上选 tottot 个点,使得这 tottot 个点与关键节点的最小距离不超过midmid,最小化 tottot

问题可以继续转化:

在树上选 tottot 个点,每个点可以覆盖距离不超过midmid的点, 最少几个点能将关键点全部覆盖

这就和将军令一样,变成最小点覆盖问题

f1[x]f1[x]xx的子树中未被覆盖的最远的关键节点的距离

f2[x]f2[x]xx的子树中最近选择节点的距离

f1,f2f1,f2转移

f1[x]=max(f1[u])+1f1[x]=\max(f1[u])+1

f2[x]=min(f2[u])+1f2[x]=\min(f2[u])+1

然后根据f1,f2f1,f2的几种情况,统计答案即可,这里就不写了,自己看代码吧(懒得写了我不会

代码见P3523 [POI2011]DYN-Dynamite

P3177 [HAOI2015]树上染色

P3177 [HAOI2015]树上染色

一棵nn个点的数,将kk个点染成黑色的,其他nkn-k个点是白色的,使黑点两两距离和加白点两两距离和最大

0n,k20000\le n,k\le 2000

一开始想的是f[i][j]f[i][j]表示以ii为根的子树中有jj个黑点的最大距离和

经过一番思考摸鱼后,这样似乎没法转移,主要问题在于没法再确定ii是黑是白的情况下计算贡献

我们考虑一下怎么计算贡献,对于两个同色点之间的距离我们可以看成是路径的和,路径上的和就是路径上边的和,我们可以通过统计每个边被同色点的路径经过次数来统计最后答案

kk为当前子节点的子树上已选择的黑点数,显然一条边被经过的次数为:

tot=k(mk)+(size[v]k)(nmsize[v]+k)tot=k\cdot(m-k)+(size[v]-k)\cdot(n-m-size[v]+k)

这样上面的状态就不太合适了,修改一下f[i][j]f[i][j]表示以ii为根的子树中有jj个黑点对答案的最大贡献

f[u][j]=max(f[u][j],f[u][jk]+f[to][k]+tote[i].w)f[u][j]=\max(f[u][j],f[u][j-k]+f[to][k]+tot\cdot e[i].w)