开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第6天,点击查看活动详情
线性DP
本节我们主要介绍线性DP的经典题型,所谓线性DP就是状态转移发生在线性结构(比如数组)上的DP,我们后面还会讲到状态转移发生在其他地方的DP,如区间DP,树形DP,数位DP等等
最长上升子序列(普通)
例题:
给定一个长度为N的数列(w[N]),求数值严格单调递增的子序列的长度最长是多少。
样例
输入格式
第一行包含整数N。
第二行包含N个整数,表示完整序列。
输出格式
输出一个整数,表示最大长度。
数据范围
1 ≤ N ≤ 1000, −1e9 ≤ 数列中的数 ≤ 1e9
输入样例:
7
3 1 2 1 8 5 6
输出样例:
4
状态表示: 我们用f[i]表示到第i为为止,最长严格递增子序列的长度
初始条件: f[0]=0,其余的f[i]至少为1
状态转移方程: 我们本次采用的方法是遍历到第i位时,找前面比i小的数j,取以j结尾的序列加入i这个数后的最长长度
因此,转移方程可以写成下面这样:
代码实现:
#include<iostream>
using namespace std;
const int N=1100;
int f[N];
int a[N];
int n;
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}
int res=-9;
for(int i=1;i<=n;i++){
f[i]=1;
for(int j=1;j<i;j++){
if(a[i]>a[j]){
f[i]=max(f[i],f[j]+1);
}
}
res=max(res,f[i]);
}
cout<<res<<endl;
return 0;
}
最长上升子序列(进阶)
题目与上一题一样,但是数据范围扩大到了100000
区别: 上一题的做法时间复杂度是O(n^2),肯定过不了
因此,我们介绍一种二分优化DP的方法
状态表示: 用f[i]表示以长度为i的最长上升子序列,末尾最小的数字
初始条件: f[1]=a[0]
状态转移方程: 我们分析一下,由于这个f[i]是一个递增数组,我们可以这样考虑问题,先定义一个长度变量cnt,并且初始化好,依次遍历原数组,当遍历到的数字比 f 目前序列末尾数大的时候,长度加一,当遍历到的数字比f 目前末尾的小的时候,长度不变,但是可以更新f 中比a[i]大于或等于的地方的末尾数,因为数越小,涨的越慢,就更有可能越长,这也有点贪心的思路
状态转移方程在这里显得不那么必要,我们直接看代码更清晰一点
代码实现:
#include <iostream>
using namespace std;
const int N = 100100;
int n, cnt;
int w[N], f[N];
int main() {
cin >> n;
for (int i = 0 ; i < n; i++) cin >> w[i];
f[cnt++] = w[0];
for (int i = 1; i < n; i++) {
if (w[i] > f[cnt-1]) f[cnt++] = w[i];
else {
int l = 0, r = cnt - 1;
while (l < r) {
int mid = (l + r) >> 1;
if (f[mid] >= w[i]) r = mid;
else l = mid + 1;
}
f[r] = w[i];
}
}
cout << cnt << endl;
return 0;
}
最长公共子序列
例题:
分析:
状态转移方程: 经过上述分析,我们不难写出状态转移方程:
代码实现:
#include <iostream>
using namespace std;
const int N = 1010;
int n, m;
char a[N], b[N];
int f[N][N];
int main() {
cin >> n >> m >> a + 1 >> b + 1;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (a[i] == b[j]) {
f[i][j] = f[i - 1][j - 1] + 1;
} else {
f[i][j] = max(f[i - 1][j], f[i][j - 1]);
}
}
}
cout << f[n][m] <<endl;
return 0;
}
最短编辑距离
例题:
分析:
状态转移方程:
代码实现:
注意:本题需要初始化边界情况
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int m, n;
char a[N],b[N];
int dp[N][N];
int main()
{
scanf("%d%s", &m, a + 1);
scanf("%d%s", &n, b + 1);
cout << a[1] << endl;
for(int i = 0; i <= m; i++) dp[i][0] = i;
for(int i = 0; i <= n; i++) dp[0][i] = i;
for(int i = 1; i <=m; i++)
for(int j = 1; j <=n; j++)
{
dp[i][j] = min(dp[i][j-1], dp[i-1][j]) + 1;
dp[i][j] = min(dp[i][j], dp[i-1][j-1] + (a[i] != b[j]));
}
printf("%d\n",dp[m][n]);
return 0;
}