线性DP:
对于我的理解,线性DP中所有存储的数据都是相邻线性的,最终所得到的路径或值都是由一层层线性关系所推导出来的
线性DP的一般考虑形式:
(1)状态表示:一般表示成f[i]或f[i][j](一维或二维或多维):一般的状态表示里面存的是抽象的<=i或<=j的集合或者满足某一条件的集合,集合可以多种多样
属性也就是f[i,j]的值,一般是f集合里的最大值或最小值或数量
(2)状态计算:状态计算说白了就是通过f[i-1]的值或f[i-1][j-1]等等已经确定的值和变量计算f[i]或f[i][j]的值,这是DP问题求解的关键所在
数字三角形:
给定一个如下图所示的数字三角形,从顶部出发,在每一结点可以选择移动至其左下方的结点或移动至其右下方的结点,一直走到底层,要求找出一条路径,使路径上的数字的和最大。
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
思考及其代码实现:
可以先将三角形看成:
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
首先思考f需要几维数组,显然需要两维,f[i][j]
f[i][j]里是什么的集合?是到达以第i行第j列的数结尾的所有路径的集合,返回的是这些集合的路径最大值
然后是状态计算:f[i][j]怎么算来?是由上一层得来的,而上一层有两个数可以到达f[i][j],如:第四层的7可以由第三层的8和1到达,所以f[i][j]=max(f[i-1][j-1],f[i-1][j])+a[i][j];
代码:
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N=510;
int n;
int a[N][N];
int f[N][N];
int main(){
cin>>n;
memset(f,-0x3f,sizeof f);
for(int i=1;i<=n;i++){
for(int j=1;j<=i;j++){
cin>>a[i][j];
}
}
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],f[i-1][j])+a[i][j];
}
}
int res=-0x3f3f3f3f;
for(int i=1;i<=n;i++)res=max(res,f[n][i]);
cout<<res;
return 0;
}
需要注意的是,距离可能是负数,所以一开始的初始距离要开负无穷
最长上升子序列:
给定一个长度为 N 的数列,求数值严格单调递增的子序列的长度最长是多少。
输入样例:
7
3 1 2 1 8 5 6
输出样例:
4
思考及其代码实现:
首先 思考f开几维,显然是一维,因为就一行数列
用a[N]数组存数列
然后思考f[i]里面存的是什么的集合:f[i]里存的是1~i中以i结尾的所有严格单调递增的子序列的集合,返回的是最长子序列的长度
最后是状态计算,f[i]可以怎么算呢?我们可以把1~i-1循环一遍,用 j 来表示1~i-1里某一个数(for ( int j=1; j<i ; j++) ) ,然后用a[i]和a[j]比较,如果a[j]<a[i]的话,说明 f[i] 可以用 f[j] 再加上1(因为 f[j] 里存的是以 j 结尾的所有严格单调递增的子序列的最大长度,如果a[i]>a[j], 那就肯定可以加个a[i]到集合里,也就是f[i]=f[j]+1;
代码:
#include <iostream>
#include <algorithm>
using namespace std;
const int N=1010;
int n;
int a[N];
int f[N];
int main(){
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=n;i++){
f[i]=1;
for(int j=0;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;
return 0;
}
最长公共子序列:
给定两个长度分别为 N和 M的字符串 A和 B,求既是 A 的子序列又是 B的子序列的字符串长度最长是多少。
输入样例:
4 5
acbd
abedc
输出样例:
3
还是老样子,f数组需要两维,因为有两个字符串嘛,
然后是f[i][j]表示的是什么:f[i][j]表示的是:1~i,和1~j中的所有公共子序列的集合,
返回的是集合的长度最大值
最后是状态计算:f[i][j]可以怎么算,如果 a[i] != b[j] 的话,那么就有三种情况:
1.a[i],b[j]都不加到集合里去,那么f[i][j]=f[i-1]f[j-1]
2.a[i]不加到集合里去,b[j]加到集合里去,f[i-1][j],但这里的 f[i-1][j] 包含两个类型,一种是没有b[j]的集合,一种是有b[j]的集合,我们不能确保f[i-1][j]一定返回的是 :a[i]不加到集合里去,b[j]加到集合里去的最大值,真的是这样吗?
一种是有b[j]的集合..
一种是没有b[j]的集合..
我们发现没有b[j]的集合不就是f[i-1][j-1]吗,这就是第一种情况,由于是求最大值,所以可以重复
所以f[i][j]=f[i-1][j]
3.a[i]加到集合里去,b[j]不加到集合里去,f[i][j]=f[i][j-1]
如果a[i]==b[j] f[i][j]=f[i-1][j-1]+1;
代码:
#include <iostream>
#include <algorithm>
using namespace std;
const int N=1010;
int n,m;
char a[N],b[N];
int f[N][N];
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>a[i];
for(int j=1;j<=m;j++)cin>>b[j];
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];
return 0;
}
最短编辑距离:
给定两个字符串 A 和 B,现在要将 A 经过若干操作变为 B,可进行的操作有:
- 删除–将字符串 A 中的某个字符删除。
- 插入–在字符串 A 的某个位置插入某个字符。
- 替换–将字符串 A 中的某个字符替换为另一个字符。
现在请你求出,将 A 变为 B 至少需要进行多少次操作。
输入样例:
10
AGTCTGACGC
11
AGTAAGTAGGC
输出样例:
4
二维数组f[i][j]..
f[i][j] 的集合是: 可以使得1~i 和1~j 的序列相等的所有操作的集合,返回的是最少的操作数
既然有三种操作方式,那么f[i][j]就有三个状态计算
如果删除第i个字符可以让1~i-1和1~j的数列相等的话,那么f[i][j]=f[i-1][j]+1;
如果在i后面添加一个字符的话可以让1~i和1~j的序列相等的话,f[i][j]=f[i][j-1]+1;
如果a[i]==b[j],f[i][j]=f[i-1][j-1];
如果a[i]!=b[j],f[i][j]=f[i-1][j-1]+1;
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N=1010;
int n,m;
char a[N],b[N];
int f[N][N];//f[i][j]代表所有把a中前i个元素变为b中前j个元素操作的集合,返回集合的最小值
int main(){
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
cin>>m;
for(int i=1;i<=m;i++)cin>>b[i];
for(int i=1;i<=m;i++)f[0][i]=i;
for(int i=1;i<=n;i++)f[i][0]=i;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
f[i][j]=f[i-1][j]+1;
f[i][j]=min(f[i][j],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;
}