10月模板清理

20241016

区间DP - 回文字串

f[i][j] 表示把 s[ij] 变成回文,最少补几个,从 f[i][j1],f[i+1][j],f[i+1][j1] 三种情况转移过来即可。感性理解一下这样的状态定义是有最优子结构的。

区间DP - 合唱队

肯定可以区间 dp,再注意到状态的转移和上一步有关,所以记 f[i][j][0/1] 表示 H[ij] 的方案数,其中 0 表示最新的一个人插进了队形左端(即 H[i]),1 就是右端(即 H[j]),根据大小关系转移即可。

高维DP - 三倍经验

唯一值得注意的是此题可以倒推,并且倒推之后的答案统计是 O(1) 的。正推的答案统计是 O(n) 的。

高维DP - 方格取数

一个很朴素的想法是把两条路的位置同时都实时记录下来,这样就好转移了,事实证明是可以的。

f[i][j][p][q] 表示第一条路走到了 (i,j),第二条路走到了 (p,q) 的最大权值和。它可以转移到四种情况,即:

  • 第一条路 往右走, 第二条路 往右走。
  • 第一条路 往右走, 第二条路 往下走。
  • 第一条路 往下走, 第二条路 往右走。
  • 第一条路 往下走, 第二条路 往下走。

分 点重合/不重合 两种情况赋予转移代价,套上 BFS, 直接转移就是 O(n4) 的,当然需要标记一下每个点是否访问过。

考虑优化成 O(n3),注意上面的方法两条路走的步数始终相同。记 f[i][j][k] 表示两条路都走了 i 步,第一条路处于第 j 行,第二条路处于第 k 行,最大权值和。起点是 (1,1),因此 (x1)+(y1)=i, 则 y=ix+2。最后特判一下只走了 1 步的情况就行,因为不满足这个式子。

环形DP - 石子合并

学到一个 trick : 区间相关的环形问题,可以把序列复制一遍接在后面,然后只推到 len=n 就可以了,最后取 maxi(1,n+1)f[i][i+n1]。这样比 n 次区间 dp 快很多,因为不同的断点位置下的区间可以使用相同的一段很小的区间。

环形DP - 环上最大子段和

最大子段和 + 前后缀max 连一下就可以。

树型DP - 没有上司的晚会

一个最大独立集问题,记 f[u][0/1] 表示选 u 或者不选 u,对于一个儿子 v,有转移:

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

f[u][1]=[v][0]+val[u]

树型DP - 二叉苹果树

树上背包,记得给 (u,v) 留一条边就可以了,并且一定要连这条边,不然转移没有意义。

状态压缩BFS - 关灯问题

只有 n 能进行状态压缩。一开始的想法是记 f[i][S] 表示前 i 个操作,强制选 i,然后得到了 S 这个集合,最少操作数。然后还“机智”地发现开个 vector[i] 表示 f[i][?] 有哪些更新出来了。

然而无济于事,因为此题 dp 的顺序并不是简单的递推(会有后效性),因此采用记忆化 BFS 进行实现。

答案: min(f[i][0])

初始化: f[0][(1<<n)1]=0,其他初始化为 inf

式子就是 f[i][S]=min(f[j][T]+1,f[i][S])T 的话就照着题目要求根据 S 推出来。

时间复杂度为 O(2nmn)

最后发现其实当前具体在哪个操作并不重要(下一步转移都是向全部操作),所以可以优化成一维。并且由于广搜良好的性质找到一个 f[0] 就直接输出就可以!

状态压缩DP技巧 - A Simple Task 「CF11D」

一开始想成树形dp了,然后发现非常的难写,因为点之间有各种路径计数,很难补充不漏。每次扩展和前一个状态的联系也不是很大。

题解是一种巧妙的状压dp。显然可以对已选点集进行状压。先考虑如何统计环的数量,对于 ij 的一条简单路径,若路径外一点 k 同时和 ij 有连边,那么就成了一个环。基于此,我们可以转化成利用 dp 统计简单路径的数量,而统计答案时在另找一个点连环。(状压没想到,但这一步转化想到了……)

这样定义出来的状态是 f[S][i][j],表示选择的点集为 Sij 的简单路径的数量。转移比较好写。空间复杂度会达到 219×192=189,267,968,存不下,时间应该还要再乘一个 19, 也爆掉了。

考虑怎么优化时空。注意到后两维其实都是我们主动加的限制,但仅仅是为了统计答案而同时维护这两维显得有些不划算。能不能利用好 S 中包含 i,j 的性质进行一些优化呢?

考虑强制规定 lowbit(S) 那一位是起点,i 作为终点,这样只要遇到一个满足 (1<<i)=lowbit(S)i, 就统计答案。

状态: f[S][i],意义如上。

答案: ans=lowbit(S)=2if[S][i]。最后记得 ans=(ansm)/2, 因为会误算 一条边和两个端点的“假环”

初始化: f[2i][i]=1,i[0,n1]

转移:

(1)ans+=f[S][i]lowbit(S) = (1 << j)(2)f[S|(1<<j)][j]+=f[S][i]lowbit(S) < (1 << j)

20241017

数位DP - Windy数 「SCOI2009」

数位dp经典题。分两步求解:

  • 预处理 f[i][j],表示有 i 位,最高位填 j,方案数。
  • dp(x) 表示 1x 有多少个 Windy 数,则答案为 dp(b)dp(a1)

a[1cnt] 表示 x 的每一位(从高到低),那么每一位的转移分 now<a[i] (后面的位随便填,可直接调用 f 统计答案)和 now=a[i] (继续往下走)两种情况转移。走到 i=1 记得 ans 还要额外 +1,就是这个数本身的答案。

20241018

数位DP - 手机号码 「CQOI2016」

可以是数位dp,也可以是高维dp……

  • 还是先说一下我超级复杂的容斥做法吧!

f1 表示 相邻 2/1个 数相同,数的个数。

f2 表示 相邻 2/1个 数相同,且同时出现 4 和 8,数的个数。

f3 表示 同时出现 4 和 8,数的个数。

则答案为 (rl+1)(f1+f3f2)

写三个 数位dp预处理 和 三个数位统计就可以啦!

狂打5KB 的AC代码,慎入

一看题解感觉自己……(哎至少是自己亲手做出来的一道蓝题!)

  • 正解是记 f[x][last1][last2][f4][f8][flag] 表示 x 位数,上一个数是 last1,上上个数是 last2,是否出现 4,是否出现 8,是否有数字连续出现 3 次,数的个数。

    转移比较典型,分 i<a[x]i=a[x] 两种情况转移。

    注意这里我们要求的是至少要连续出现 3 次了,和我上面的做法是反着的。

    AC代码(带注释)

20241023

数位DP - Round Numbers 「USACO06NOV」

首先读题读对:二进制下数位dp

还是很典的数位dp,先写预处理,再写 dp(x),最后 dp(r)dp(l1)

一开始我的定义是 f[i][j] 表示 i 位数,含有 j0 的圆数个数。由于包含了前导零的情况,转移容易算重,非常不好写……

考虑增加一位区分有没有前导零,记 f[i][0/1][k] 表示 i 位数,最高位是 0/1,含有 k0 的圆数个数。接着来考虑贡献组成:

设当前求解的数为 x,有 cnt 位。

  • Case 1:含有前导零,贡献为

(1jicnt)(j>ij)f[i][1][j]

  • Case 2:不含前导零,最高位就强制填 0。从高位到低位统计,若 a[i]=0,直接过;若 a[i]=1,可以选择填 0 然后算贡献。

(数位dp终于做完了……)

图上DP - 跑跑

由于空间跑路器的存在,直接跑最短路再 popcount 是错滴。

看到 2k,想到这种题应该和倍增有关系。

考虑一个情景:

  • 如果 (ik)(kj) 都有一条长为 20 的路,那么 (ij) 就有一条长度为 21 的路!然后用跑路器就可以 1s 跑完,即 i,j 之间存在一条权值为 1 的边。

这个情景告诉我们,如果我们能知道 哪些点之间能用跑路器,即 “存在 2k 的路径” ,并给他们在新图上连一条权值为 1 的边,就差不多做完了。

现在考虑怎么求 “点之间存不存在这种路径”。

图上/期望DP - 绿豆蛙的归宿

有向图概率期望dp。

这其实是 DAGdp。转移顺序就确定了。

然后就是期望 dp 的部分。期望 dp 很多状态都是倒着定义的,形如 “当前在某个中间状态 i,要到终止态 n 还需要的期望值”

此题就是 f[u] 表示当前到达了 u,走到 n 还需要的期望步数。

因为转移顺序中没有回头路可走,所以不用考虑自转移的情况。

f[u]=f[v]+w(u,v)d[u]

d[u]u 的入度。

“自转移” 参见这道题:百事世界杯之旅-luogu

图上DP - 采蘑菇

因为说了是有向图,所以感觉就很能dp,但是不是 DAG,遇到环就dp不了了。

再思考,进了一个环肯定要把贡献全部拿完才会出来,再根据“恢复系数”,可以预处理每个环的贡献,最后环缩点。

缩点后整张图就变成一张 DAG 了,就可以愉快的dp了。

最后注意因为 s 不一定是入度为0的点,所以要么 先一次 dfs 把涉及的子图全部跑出来,再 topsort;要么直接 bfs,每个点可能重复入几次(没被卡时间)

时间复杂度瓶颈在算环的贡献,最坏情况是 O(mlogp1w)p 是恢复系数,w 是初始蘑菇数。

DP优化 - 单调队列优化 - 能量收集

f[i][j] 表示走到 (i,j),最大收益。

f[i][j]=maxjtkj+tf[i1][k]+val[i][j]

感觉既可以 st 表,也可以单调队列优化。2000ms嘛,O(n2logn) 能卡。

考虑一组 a<bf[i1][a]<f[i1][b],那么 a 就永远无法成为贡献点了,因为位置不如 b 优,而且值也没有 b 大。

由此,我们需要维护一个 f[i][j] 递减的单减栈,队尾维护单调性,队首清理过期元素即可。

20241024

单调队列优化 - 跳房子「NOIP2017 普及组」

考虑已知跨步的范围后怎么做。记 f[i] 表示跳到第 i 个格子,最大分数,容易写出转移:

f[i]=maxld[i]d[j]rf[j]+a[i]

显然 g 可以二分,直接做是 O(n2logn) 的。

考虑单调队列优化dp,维护一个 f[i] 单调递减的单调栈。

一个细节是若 d[i+1]d[i]<l 是不能直接把 i 加入的,要记一个 nw 表示现在加到哪一个了,每次判断距离是否 l 了。总之细节比较多。

自己写过之后看了一下别人的代码,发现很多东西可以合并。比如 nw0 开始而不要从 1 开始,且当 headtail 时才更新 f[i],这样开头涉及 0 的部分就不会出错了。代码也简洁多了。

并且单调队列的过期清理应该也只需要一次,放在插入元素之后搞一次。如果 f[nw] 真的在 f[q[tail]] 处遇到了这些过期元素,那也是没问题的,因为要么就是 f[nw]>=f[q[tail]],这些东西本来就会弹出去。要么就是 f[nw]<f[q[tail]],那之后过期清理也就对了。

AC代码

20241025

有依赖的背包问题 - AcWing

发现自根往下选并不好决策,考虑倒着做。

定义:记 f[i][j] 表示 在 i 的子树内,选的总体积为 j 且必选 i,最大价值。

答案:max1ivmaxf[root][i]

转移:f[i][j]=maxjka[i]f[i][jk]+f[son[i]][k]

初始化:f[i][a[i]]=b[i],其他的设为 inf

void dfs(int u){
	f[u][a[u]] = b[u];
	for(auto v : G[u]){
		dfs(v);
		G(i, vmax, a[u]){
			G(j, i, a[v]){
				if(f[u][i - j] >= 0 && f[v][j] >= 0)
					f[u][i] = max(f[u][i], f[u][i - j] + f[v][j]);
			}
		}
	}
}
posted @   superl61  阅读(10)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示