线性DP总结 吧。。
最近感觉做了很多线性DP和背包的简单题,又感觉背包也珂以算进线性DP,所以就一起写了吧
下面的几个分类模型来源于AcWing
基础的线性DP~
包括几个基础的模型~
M 数字三角形类的模型
线性DP弟中弟(数字三角形)
尽管它的思路非常的简单,但是毕竟是一个用二维数组设计状态来DP(或者说递推,这个弟中弟甚至不配被称作DP)的开端。
直接来看例题的拓展~
只是稍微过渡一下,毕竟多了个 min/max(可能这才是DP开端8)
这道经久不衰的线性DP进阶例题其实真的很有意义的
我们先来观察(O(n^4)) 的暴力转移:
定义 (f[i][j][x][y]) 为两条路径,一个从((1,1))走到了((i,j)),
一个从((1,1))走到了((x,y)) 珂以获得的贡献最大值
整个转移也很简单,就是在 x=i 而且 y=j 时减去重复贡献的点就好了
虽然这是一个早期(2000) tg的一道原题,但是 0202 年谁还考这种暴力DP题a,于是我们要考虑优化:
1.空间优化:
我们的两条路径都是同时拓展的,因为只能向下或者向右走,我们完全珂以通过一个路径长度 k 用两条路径终点的横坐标算出纵坐标。
时间 (O(n^4)) 空间 (O(n^3))
2.空间的进一步优化:
发现每一次的状态转移 ( k ) 只与上一层 (k-1) 有关,诶,这不就直接滚了么 完全珂以套用滚动数组的思想,用 ^1 取出上一层的值再转移
空间(O(n^2))
虽然说大多数的二维DP的时间是很难优化的
但是空间在一些情况下是很好优化的:
可以通过以下几种思路优化空间:
1.通过设计不同状态来优化空间
2.通过同一种状态的不同表示方法来优化空间
3.发现状态转移只与上一层有关,直接滚动数组
还有一些线性转移 而且用一维数组设计状态的DP,
珂以用数据结构优化时间,这到时候再写8(估计到时候都退役了)
D 上升子序列模型
1.简单的 (n^2) 设计方程:
设 (f[i]) 为以 (a_i) 结尾的最长上升子序列的长度
整个的状态转移也就显而易见了
f[i]=max(f[i],f[j]+1) (a[j]<a[i])
通过上面这个简单的转移方程珂以类比到最长(不下降,不上升,上升)子序列中
有了这个 (n^2) 你就可以尝试解决很多简单题了,
题目中说想要移除最少的人,简单来说也就是保留最多的人
观察题目条件珂以发现:能保留在一个人左侧的人一定从左到右形成一个上升序列,同理右侧也从右到左满足珂以形成一个上升序列。
所以你正着跑一遍最长上升,再倒着跑一遍最长下降
那么答案 = (n-max(f1[i]+f2[i]-1))
-1 是因为两个序列都有一个 (a_i)
考虑优化时间
(n^2) 的出来了,而且又是一个一维数组设计的DP,空间很难优化了,那么考虑优化时间:
(值域线段树优化DP找区间最大值简单粗暴快捷)
考虑一个数组存储整个最长上升子序列:
那么对于一个从前向后扫将 扫描到的数 加入这个数组中的过程:
显而易见:如果说这个数 > 数组的尾部数,就可以将它加入数组
那么一个贪心策略也就珂以想出来:
如果让末尾的数更小,就可以让更多的数加入这个序列
那么对于 (leq) 末尾数的数
考虑将它替换数组中的一个数,答案是替换第一个 > 它的数,
(感性理解,我也不会证明)
1.替换了不影响当前最长上升子序列的长度
2.替换了 > 它的数就可以让更多的数加入
因为整个数组中是单调的,所以可以采用二分解决替换的问题
for(int i=2;i<=n;i++){
if(a[i]>f[ans]){ans++;f[ans]=a[i];}
else{f[upper_bound(f+1,f+ans+1,a[i])-f]=a[i];}
}
首先考虑暴力:
设计状态(f[i][j]) 表示(a[1~i])与(b[1~j])
珂以组成的最长公共上升子序列长度:
状态转移过程:
for (int j = 1; j <= n; j ++ ){
f[i][j] = f[i - 1][j];
if (a[i] == b[j]){
int maxn = 1;
for (int k = 1; k < j; k ++ )
if (a[i] > b[k]) maxv = max(maxn, f[i - 1][k] + 1);
f[i][j] = max(f[i][j], maxn);
}
}
时间复杂度:(O(n^3))
考虑优化时间:
发现 maxn 是 满足 a[i] > b[k] 中整个(b[k]) 集合中 (f[k]+1) 最大值
所以珂以直接将 maxn 提出去
for (int i = 1; i <= n; i ++ ){
int maxn = 1;
for (int j = 1; j <= n; j ++ ){
f[i][j] = f[i - 1][j];
if (a[i] == b[j]) f[i][j] = max(f[i][j], maxv);
if (a[i] > b[j]) maxv = max(maxv, f[i - 1][j] + 1);
}
}
时间复杂度:(O(n^2))
在考虑优化空间:
又发现 这个转移只跟上一层有关。。所以又珂以滚动数组了。。
for(int i=1;i<=n;++i){
int maxn=0 ;
for(int j=1;j<=n;++j){
if(a[i]>b[j]){maxn=max(maxn,f[j]);}
else if(a[i]==b[j]){f[i]=max(f[j],maxn+1);}
}
}
(个人觉得滚动数组版本要比二维数组的版本要好想一点。。。)
背包 ~ ~ ~ ~
简单的 01 背包~ ~ ~
转移非常的简单~ ~ ~ ~
for(int i=1;i<=m;i++){
for(int j=0;j<=t;j++) {
if(j>=w[i])dp[i][j]=max(dp[i-1][j-w[i]]+value[i],dp[i-1][j]);
else dp[i][j]=dp[i-1][j];
}
}
诶,我们又发现这个东西只跟上一层状态有关了,诶这就直接滚动了
(需要注意的是 01 背包需要倒序枚举,因为他需要的是 i-1 层的状态,如果正序枚举的话可能会用到第 i 层的状态
完全背包:
就滚动数组版的 01 背包正序枚举就好了,因为它正好需要第 i 层的状态。。。。
持续咕咕咕