文章目录
线性DP
数字三角形 、最长上升子序列 、最长公共子序列、最短编辑距离
一、数字三角形
给定一个如下图所示的数字三角形,从顶部出发,在每一结点可以选择移动至其左下方的结点或移动至其右下方的结点,一直走到底层,要求找出一条路径,使路径上的数字的和最大。
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
状态表示
在第i层的第j个元素,包含该元素的路径上的数字的最大和
f[i][j] = max(f[i - 1][j - 1] + a[i][j], f[i - 1][j] + a[i][j]);
状态更新
- 包含左上元素的路径上的数字的最大和
- 包含右上元素的路径上的数字的最大和
#include<iostream>
using namespace std;
const int N=510, INF=1e9;
int n;
int a[N][N];
int f[N][N];
int main(){
cin>>n;
for (int i=1;i<=n;i++ )
for (int j=1;j<=i;j++ )
cin>>a[i][j];
for(int i=0; i<=n;i++ )
for(int j=0; j<=i+1;j++ )
f[i][j]=-INF;
f[1][1]=a[1][1];
for(int i=2;i<=n;i++ )
for(int j=1;j<= i;j++ )
f[i][j]=max(f[i-1][j-1]+a[i][j],f[i-1][j]+a[i][j]);
int res=-INF;
for (int i=1; i<=n;i++ ) res = max(res,f[n][i]);
cout<<res;
return 0;
}
二、最长上升子序列
给定一个长度为 N的数列,求数值严格单调递增的子序列的长度最长是多少。
动态规划 O(n^2)
#include<iostream>
using namespace std;
const int N=1010;
int a[N];
int f[N];
int MIN=1e9;
int main(){
int n;
cin>>n;
for(int i = 1;i <= n;i ++ ) cin>>a[i];
for (int i = 1; i <= n; i ++ )
{
f[i] = 1; // 只有a[i]一个数
for (int j = 1; j < i; j ++ )
if (a[j] < a[i])
f[i] = max(f[i], f[j] + 1);
}
int res = 0;
for (int i = 1; i <= n; i ++ ) res = max(res, f[i]);
cout<<res<<endl;
return 0;
}
状态表示
以第 i 个元素结尾的最长子序列的长度。
状态更新
- 若元素能加入第最长子序列的结尾,则进行状态更新
f[i] = max(f[i], f[j] + 1);
动态规划+二分 O(n*logn)
#include<iostream>
#include<algorithm>
using namespace std;
const int N=100010;
int n;
int a[N];
int q[N];
int main(){
cin>>n;
for(int i=0;i<n;i++) cin>>a[i];
int len=0;
q[0]=-2e9;
for(int i=0;i<n;i++){
int l=0,r=len;
while(l<r){
int mid=l+r+1>>1;
if(q[mid]<a[i])l=mid;
else r=mid-1;
}
len=max(len,r+1);
q[r+1]=a[i];
}
cout<<len;
return 0;
}
状态表示
长度为 i 的子序列的最小结尾元素。
状态更新
- 在长度为 len 中,对不同长度的子序列的最小结尾元素进行状态更新,若子序列变长,则更新最长子序列长度。
q[r+1]=a[i];
len=max(len,r+1);
三、最长公共子序列
给定两个长度分别为 N和 M的字符串 A和 B,求既是 A 的子序列又是 B 的子序列的字符串长度最长是多少。
状态表示
以第i个元素为结尾、以第j个元素的为结尾的两个序列的最长公共子序列长度。
状态更新
- 最长公共子序列包含第 i 个元素,包含第 j 个元素。被
f[i-1][j-1]
包含,此时两个对应的新元素匹配,长度 + 1。 - 最长公共子序列不包含第 i 个元素,包含第 j 个元素。被
f[i-1][j]
包含。 - 最长公共子序列包含第 i 个元素,不包含第 j 个元素。被
f[i][j-1]
包含。 - 最长公共子序列不包含第 i 个元素,不包含第 j 个元素。被
f[i][j-1]
或f[i-1][j]
包含。
f[i][j]=max(f[i-1][j],f[i][j-1]);
if (a[i]==b[j]) f[i][j]=max(f[i][j],f[i-1][j-1] + 1);
#include<iostream>
using namespace std;
const int N=1010;
char a[N],b[N];
int f[N][N];
int main(){
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=m;i++) cin>>b[i];
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
f[i][j]=max(f[i-1][j],f[i][j-1]);
if (a[i]==b[j]) f[i][j]=max(f[i][j],f[i-1][j-1] + 1);
}
cout<<f[n][m];
return 0;
}
四、最短编辑距离
给定两个字符串 A 和 B,现在要将 A 经过若干操作变为 B,可进行的操作有:
删除–将字符串 A 中的某个字符删除。
插入–在字符串 A 的某个位置插入某个字符。
替换–将字符串 A 中的某个字符替换为另一个字符。
现在请你求出,将 A 变为 B 至少需要进行多少次操作。
状态表示
以第 i 个元素为结尾的序列转变为以第 j 个元素的为结尾的序列的最少操作次数。
状态更新
- 删除:前 i - 1 个元素与前 j 个元素相同。f[i-1][j]+1
- 插入:前 i 个元素与前 j - 1 个元素相同。f[i][j-1]+1
- 替换:前 i - 1 个元素与前 j - 1 个元素相同。f[i-1][j-1]+1
- 对应元素相同则无需操作。
f[i][j]=min(f[i-1][j]+1,f[i][j-1]+1);
if(a[i]==b[j]) f[i][j]=min(f[i][j],f[i-1][j-1]);
else f[i][j]=min(f[i][j],f[i-1][j-1]+1);
因为有边界问题,故单独考虑A为0个字符(只插入)和B为0个字符(只删除)的情况。
for(int i=0;i<=m;i++) f[0][i]=i;
for(int i=0;i<=n;i++) f[i][0]=i;
#include<iostream>
using namespace std;
const int N=1010;
char a[N],b[N];
int f[N][N];//存储所有操作方式的操作次数的最小值。
int main(){
int n,m;
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
cin>>m;
for(int j=1;j<=m;j++)
cin>>b[j];
for(int i=0;i<=m;i++) f[0][i]=i;
for(int i=0;i<=n;i++) f[i][0]=i;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
f[i][j]=min(f[i-1][j]+1,f[i][j-1]+1);
if(a[i]==b[j]) f[i][j]=min(f[i][j],f[i-1][j-1]);
else f[i][j]=min(f[i][j],f[i-1][j-1]+1);
}
cout<<f[n][m];
return 0;
}