qbxt dp 笔记
DP
最优化原理
- 利用最优解原理,从小问题答案推到大问题的答案
- 状态表示 & 最优化值
状态表示
- 具有最优化子结构
- 具有简洁性
- 能够同时全面的描述一个局面
优化
- 减少状态维数
- 加速状态转移——数据结构优化或性质分析
记忆化搜索
- 搜索时将答案记下,当下一次重复计算时直接调用结果
记忆化搜索的效率可能相对普通 dp 效率更高,其搜到的结果必然是有用的
拓扑图 dp
- 在拓扑图上求关于所有路径的某种信息之和,按拓扑序沿着有向边转移即可
序列dp
- 以
F[i] 表示以i 为结尾的最优值的方案数 - 一般枚举序列端点进行转移
单调队列优化
对于两个决策
若
若
每次再队列末尾插入删除一个数或者再队首弹出一个数,保证队列单调递增
括号序列模型
-
给定一个长度为n的仅包含左括号和问号的字符串,将问号变成左括号或右括号使得该括号序合法,求方案总数。例如())与()()都是合法的括号序列。
对于括号序列问题,往往是把左括号看成
+1 ,将右括号看作-1 ,我们只需要保证任意一个前缀大于等于0 ,总和为0 若
i+1 为左括号,则能转移到dp[i+1][j+1] 若
i+1 为右括号,则能转移到dp[i+1][j-1] 若
i+1 为问号,则能转移到dp[i+1][j-1] 和dp[i+1][j+1] -
给出一些括号序列,要求选择一些序列拼接成一个合法的括号序列,使得总长最大
每一段括号序列都可以等价为仅由不匹配括号组成的序列
对于右括号,即求前缀和最小值,同理,对于左括号,即求后缀和最大值
Catalan数
-
- 有
n 对括号形成的合法的括号序列有多少 -
-
凸多边形的三角形划分数
方程
f[n] = \sum_{i=0}^{n-1}{f[i] * f[n-1-i]} 通项:
\frac{C(2n,n)}{n+1}
区间dp
区间 dp 一般就是设
-
给定一个字符串,求其回文子串的数量
状态:
dp[i][j] 表示i 到j 的回文子串数量转移
dp[i][j] = dp[i][j-1] + dp[i+1][j] -dp[i+1][j-1] + ([i,j])
环形问题
-
断环为链,然后将链复制一遍并接在原链的后面
-
之后做区间 dp ,最后去答案就是找
dp[i][i+n-1] 里面取最优即可 -
能量项链
设
f[j][i]\leq q 表示左端点为j 号珠子,右端点为i 可获得的最大能量转移:
f[j][i] = max(f[j][i],f[j][k]+f[k+1][i]+a[j]\times a[k+1]\times a[i+1])
背包 dp
-
给出一些物品,每个物品具有一些价值参数和花费参数,要求再满足条件限制下最大化价值或者方案数
-
0/1 背包:
dp[i][j]=max\{ dp[i-1][j] ,dp[i-1][j-w[i]]+v[i]\} 输出方案:倒推;用一个数组记录选的上一个物品,递归输出
方案数:若 dp 值一样,则方案数相加,负责取两者中较大值的方案数
-
完全背包:
dp[i][j]=max\{ dp[i-1][j] , dp[i][j-w[i]]+v[i] \} -
多重背包
暴力:转移时枚举该物品选多少,复杂度:
O(N * M * t[i]) 优化一:将
t[i] 拆成1,2,4,8,...,t[i] - 2^k ,我们可以发现这些组能够拼成0... t[i] 的每种情况,之后我们就成了n * log(t[i]) 个物品的 0/1 背包,复杂度为O(n*log(t[i])*m) 优化二:对于方程
dp[i][j] = max(dp[i-1][j-w[i]*k] + v[i]*k | k \leq t[i]) 对于第二维,我们的
j 和能转移过来的j-w[i]*k 在模w[i] 意义下时同余的,也就是说我们可以对于第二维按照模w[i] 进行分类,不同类之间不会相互影响设
f[j] = dp[i-1][j*w[i]+r] ,r 是我们枚举模w[i] 的一个类,则dp[i][j*w[i]+r]=max(f[k]+(j-k)*v[i] | j-k \leq t[i]) dp[i][j*w[i]+r]=max(f[k]-k*v[i] | j-k \leq t[i]) + j*v[i] 实际上即为滑动窗口取最值问题,直接单调队列优化即可
-
分组背包: 一共有
n 组,每组物品中只能选择一个物品
对于背包问题的实际应用,要寻找什么是容量,什么是物品,什么是物品的费用,什么是物品的价值
-
例:安排房间,已知容纳人数与价格,安排一定数量的人住,满足不同性别的人不是夫妻不能住同一房间,一个房间如果住夫妻,就不能再住其他人,求最小花费
设
f[i,j,k,0] 表示前i 个房间住j 名男性k 名女性且没有夫妇住在一起的最小花费$$ f[i,j,k,0]=min(f[i-1,j,k,0],f[i-1,j-v[i],k,0]+p[i],f[i-1,j,k-v[i],0]+p[i]) $$ $$ f[i,j,k,1]=min(f[i-1,j,k,1],f[i-1,j-v[i],k,1]+p[i],f[i-1,j,k-v[i],1]+p[i],f[i-1,j-1,k-1,0]+p[i]) $$
一类套路题
给你
按照
将物品按照
从上一阶段向下一阶段转移时,将
复杂度:
整数划分问题
-
把
n 划分为k 个正整数的方案数法一:暴力 dp
法二:设
dp[i][j] 表示把i 划分为j 个数的方案数则可以得到:
dp[i][j] = dp[i-j][j] + dp[i-1][j-1] -
法一:暴力,本质是 0/1 背包 法二:$dp[i][j] = dp[i-j][j] + dp[i-j][j-1] -
把
n 划分为k 个不大于m 的互不相同正整数的方案数dp[i][j]=dp[i-1][j]+dp[i-j][j-1]-dp[i-(m+1)][j-1]
例:给定一个杠杆,等距均匀分布一共
本题即为从
显然求出来
状态转移注意
- 分类讨论不重不漏
- 讨论时要保证划归到的 dp 状态已经求出来,而不是还未求的
- 分类尽量少,使转移更加迅速
数位 dp
- 统计符合限制的数字的个数
- 通过记录决定了前多少位以及大小关系来 dp
- dp 处理不同位数的数的个数,再 dp 统计
- 善用不同进制来处理
一般形式
求区间
用不同的进制来处理,一般问题为 10 进制和二进制的数位 dp
例题1
求满足存在连续位为
考虑位为
首先考虑一种特殊情况:
设
假设到了第
如果前面没有出现
如果前面出现了
首先我们可以枚举哪一位,然后再枚举这一位是多少,对应的
总复杂度
一般使用记忆化搜索来实现
例题2
设
枚举
小技巧
- 注意
n=0,m=0 特殊处理 - 正难则反
- dp初始化 memeset 要置为
-1 - 记忆化搜索可以剪枝
- 注意特殊处理前导
0
树形 dp
- 以每棵树为子结构,在父亲节点合并,注意树具有天然的子结构
- 巧妙利用 Bfs 或 Dfs 序,可以优化问题,或得到好的解决方法
- 可以与树上的数据结构相结合
- 一般设
f[u] 表示u 子树的最有价值 - 在树结构上,求最优值,求方案等 dp 问题,就可以考虑是树形 dp
例一
求树的最大独立集(最大独立集指一个最大的点集使得其中的点两两没有边相连)
设
例二
求树的直径
一条路径可看为路径中一边与该边两端点到对应直径端点的距离和,在DP中处理最大值,并维护最长链即可
for(int i=head[x];i;i=s[i].nxt)
{
ll y=s[i].v;
ll w=s[i].w;
if(vis[y]) continue;
dp(y);
ans=std::max(ans,d[x]+d[y]+w);
d[x]=std::max(d[x],d[y]+w);
}
例三
给定一棵有
设
复杂度:
例四
给出一棵二叉树,点数为
设
例五
给出一棵树,每个点上都有
考虑
转移,首先要加上从
最终答案为
复杂度
树上背包
给出一棵
DFS 序上 dp
在 DFS 序上 dp ,如果不选一个点,则跳过代表他的子树的 dfs 上连续一段
设
设
不选:
选:
两种情况取最大值即可 (
another
我们不是每次将孩子与自己合并,而是直接把 dp 数组复制再传给孩子,再从孩子递归下去,最后原来自己的 dp 数组和孩子的 dp 数组直接再对应重量的价值中取 max
换根树形 dp
一棵边带权值
我们需要处理子树的信息,同时我们还需要知道父亲那一枝的信息
进行两边 dfs
- 一遍求出每个点子树内的信息
- 另一遍从根往下遍历的时候计算维护出每个点父亲枝的信息
设
在前面的题目当中,以
我们发现如果我们知道了
基环树
即为在树上加一条边(
-
分为两部分处理:对树处理和对环处理
断开环上一条边,枚举边端点的情况,然后当树来处理
或先把环上挂着的树的信息都计算完,然后转化为序列问题,或者说是环形的序列问题
-
DFS 找环
对于一个图,它 dfs 树上的每一个返祖边和的 dfs 树上的路径构成环,即要找到返祖边即可
-
基环树内向:有向图有且只有一个出度,构成类似基环树结构,并且每个点的节点方向指向环内
任何一个点沿着唯一出边走都会走到环上
利用这个性质可以随便选一个点再通过一个简单循环找到基环树的环
-
基环树外向:与内向相反
将所有边反向后,利用基环树内向找环
例一
则
即为所求
考虑将
当
反之,这个可行区间只会变大,不会缩小,所以直接记录
dp 常见优化
- 加速状态转移
- 前缀和优化
- 单调队列优化
- 树状数组或线段树优化
- 精简状态:通过对题目本身性质的分析,去省掉一些冗余的状态,对分析能力要求更高
前缀和优化
求长度为
设
如果其插在最后,则
即为从前向后更新
考虑
我们设:
则
通过记录前缀和,将转移优化为
在 dp 的基础上,做容斥原理,利用整数划分求解即可
单调队列优化
-
单调队列维护 dp ,一般把
O(n) 转移的式子通过单调队列优化成一个均摊O(1) 的式子 -
形如:
dp[i] = max{f(j)} + g[i] ,且j 取值是一段连续的区间,区间端点的两端随着i 增大而增大的区间 -
若可行区间左端点固定,我们就可以通过记录前缀最小值来处理
-
$$ dp[i]=max\{dp[j]+f(j)\} + g[i] $$ $$ dp[level][i]=max\{dp[level-1][j] + f(j)\} + g[i] $$ -
例 我们考虑在未来
T 天内的某只股票,第i 天的买入价为每股AP_i ,第i 天的卖出价为每股BP_i (对于每个i ,都有AP_i>=BP_i ) ,每天不能无限制地交易,规定第i 天至多只能购买AS_i 股,至多卖出BS_i 股。另外规定在两次交易(买入或者卖出均算交易)之间,至少要间隔W 天,一个人的手里的股票数不能超过MaxP
设
对于买入:我们可以变形:
则只有最后一项与
故可以进行单调队列优化,维护
差异
- 前缀和与单调队列都和区间有关
- 但单调队列优化在最优化 dp 值时有关,而前缀和在计数时和一段区间有关
树状数组与线段树优化
求最长上升子序列
状态转移:
将
总结
一般先设出 dp 式子,然后观察满足优化模型,就可以进行优化
分析状态
-
求最长上升子序列
我们是找比
a[i] 小的a[j] 里面,dp[i] 的最大值我们设
h[k] 表示dp[j]==k 的所有j 当中最小的a[j] 所以我们对于一个 $a[i]$ 就可以找到最大的 $k$ ,满足 $h[k]$ 是小于 $a[i]$ 的,然后 $f[i] = k + 1$ ,查找过程可以二分加速 -
传纸条
一个
m 行n 列的矩阵,进行纸条传递,从(1,1) 运到(m,n) ,希望最大化路径权值总和暴力:
dp[i][j][k][l] ,O(n^4) 转移优化:我们可以通过枚举步数,计算出剩余的一个坐标,降至
O(n^3)
状态压缩 dp
- 当普通的 dp 状态维数很多,但每一维总量很少时,可以将多维状态压缩为一维来记录
- 特征:某个给定信息的范围非常小
例一
给出
设
转移:
状压 dp 本质是暴力记录状态,其中有一维是指数级的
例二
则:
复杂度:
位运算
对于第六条,复杂度为:
拓扑序个数问题
给定一张拓扑图,求这张拓扑图有多少中不同的拓扑序
例三:愤怒的小鸟
设
优化:从小到大枚举
例四
一共
首先,将鹿角进行排序,判断合法与否
操作上限为
首先,将鹿进行分组,通过组的大小减一,来满足题目要求的条件,注意对于一个组,我们将所有角排序,检查是否合法
选尽量多的合法组并起来等于全集,枚举子集的状态压缩 dp 即可
总结
- 选择不相交集合并起来等于全集,每个选择的集合都要求可行,则通常用状压 dp 解决
- 这种 dp 可以控制最低位的
1 必选来减少 dp 的重复计算,使得我们不需要枚举全部子集,优化时间
例五
现在有一个具有
设
按照旅行商问题求解即可
例六
给出一个
令
时间复杂度
-
N \leq 20$ -> $2^n$ , $n*2^n -
-
N \leq 15$ -> $3^n$ , $n*3^n
斜率优化
- 加速状态转移
输出
设
则:
复杂度:
假设在求解
则满足:
展开并移项合并
将
故当
把
当一个数的 dp 值求完了,它的
我们考虑:
那么我们可以考虑他们的关系:
本题可以转化为求在每棵苹果树下的概率
那么第一步走每条边的概率都为:
故:
同理:
于是
例二
你有一个英雄和若干奴隶主,对方每次攻击会从你的英雄和奴隶主中随机选一个造成一点伤害,奴隶主受到攻击后,体力为 0 则死亡,否则若场上奴隶主少于 7 个,则召唤一个 3 点血量的奴隶主,有
设
转移只需要枚举当前攻击到的时英雄还是某种奴隶主即可
初始:
每次询问均可
例三
NOIP 2016 换教室
首先可以 floyd 求出任意两点间最短路径
可以想到一个显然的dp状态:
转移:
例四
有
设
例五
一个长度为
首先
如果i之前极长1长度为x,下一个位置再是1, 则这个增量就是
而增量的期望就是
也就是再存一下
设
设
经典问题
-
编号为
1 ~n 的人排成一排,问有多少种排法使得任意相邻两人的编号之差不为1 或-1 排列计数问题考虑把数从小到大插入的过程进行dp
设
f[i][j] 表示1 ∼i 的排列,有j 组相邻的相差1 ,且i 和i−1 不相邻的方案数设
g[i][j] 表示1 ∼i 的排列,有j 组相邻的相差1,且i 和i−1 相邻的方案数那么考虑插入
i+1 的位置,有:不破坏空位且不与i 相邻、不破坏空位且与i 相邻、破坏空位且不与i 相邻、破坏空位且与i 相邻(只发生在g 的转移)4种分别推一下方案数即可,最后的答案就是
f[n][0] 时间复杂度
O(n^2) -
圆桌上摆放着
n 份食物,围成一圈,第i 份食物所含热量为c[i] 。相邻两份食物之间坐着一个人,共有n 个人。每个人有两种选择,吃自己左边或者右边的食物。如果两个人选择了同一份食物,这两个人会平分这份食物,每人获得一半的热量。假如某个人改变自己的选择后(其他n-1 个人的选择不变),可以使自己获得比原先更多的热量,那么这个人会不满意。请你给每个人指定应该吃哪一份食物,使得所有人都能够满意。带环的问题,包括基环树,很多就是断环为链,然后枚举一端的情况,然后退化成序列上的问题
肯定是要先枚举一下最开始的情况,设
f[i][S] 表示第i 份食物被两个人吃的状态为S 是否有可能,这里的S 表示的是:两个人都没有吃;左边的人吃了右边的没吃;左边的没吃右边的吃了;左右两边都吃了。- 一个人只能吃一边。
- 你要使得中间这个人,变了吃的方向,不会吃的热量更多。
对于这种环形的 dp 问题,很多时候就是先强制第一个必选什么做一遍 dp,再按照另一种情况做一遍 dp,取最优值
这个问题中我们发现只有相邻的有影响,这才能保证动态规划的无后效性,就是不会影响之前的选择,往往这种情况才能选择用动态规划