线性DP
理解
动态规划是指把一个问题分成若干个子问题,通过局部最优解得到全局最优的一种算法策略或者说一种思想方法,简单来讲就是用一个数组表示我们要求的问题的答案:由前一个问题的答案推出另一个问题的答案。
dp一般有三个步骤:
1、设计状态:设计合适的数组以及相应的含义。
2、状态转移方程:从已知问题的答案推出当前问题的答案。
3、确定边界条件:递推的初值或者说记忆化搜索的回溯条件,以及各个数组的初值。
线性DP问题是指递推方程具有明显的线性关系,有一维线性和二维线性
数字三角形
最长上升子序列
题目描述:给定一个长度为 N 的数列,求数值严格单调递增的子序列的长度最长是多少。
思路:
- 状态表示:
f[i]
表示从第一个数字开始算起,以w[i]
结尾的最长的上升序列, - 状态计算:在
w[i] > w[j]
时,f[i] = max(f[i], f[j] + 1)
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1100;
int a[N];
int f[N]; // f[i]表示前i个数字的最长上升子序列
int main()
{
int n;
cin >> n;
int res = 0;
for(int i = 1;i <= n;i++) scanf("%d",&a[i]);
for(int i = 1; i <= n;i++)
{
f[i] = 1;
for(int j = 1; j < i;j++)
{
if(a[j] < a[i]) f[i] = max(f[i],f[j]+1); // 因为j<i,所以f[j]是更新过的
}
res = max(res,f[i]);
}
cout << res;
return 0;
}
最长公共子序列
题目描述:给定两个长度分别为 N 和 M 的字符串 A 和 B,求既是 A 的子序列又是 B 的子序列的字符串长度最长是多少。
思路:
注意:子串是连续的,子序列可以不连续
- 状态表示:设
dp[i][j]
表示A的前i个字符 和 B的前j个字符 的最长公共子序列长度 - 状态转移:
A[i] == B[j]
,那么dp[i][j] = dp[i-1][j-1] + 1
,前面的最长公共子序列加上最后这一个A[i] != B[j]
,那么A[i]
和B[j]
肯定不会同时出现在最长公共子序列中,可以分别与A的前 i - 1个和B的前j - 1个字符比较,dp[i][j] = max(dp[i-1][j], dp[i][j-1])
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 1010;
int f[N][N]; // f[i][j] 表示s1前i个字符和 s2前j个字符公共子序列的最大值
char s1[N], s2[N];
int main()
{
int m,n;
cin >> m >> n; // 字符串的长度
cin >> s1+1 >> s2+1;
/ f[1][0] = f[0][1] = 0;
for(int i = 1;i <= m;i++)
{
for(int j = 1; j <= n;j++)
{
if(s1[i] == s2[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[m][n];
return 0;
}
最短编辑距离
题目描述:
给定两个字符串 A 和 B,现在要将 A 经过若干操作变为 B,可进行的操作有:
删除–将字符串 A 中的某个字符删除。
插入–在字符串 A 的某个位置插入某个字符。
替换–将字符串 A 中的某个字符替换为另一个字符。
现在请你求出,将 A 变为 B 至少需要进行多少次操作。
思路:
- 状态表示
dp[i][j]
- 集合:所有把
a
中的前i个字母
变成b
中的前j个字母
的操作集合 - 属性:所有操作中
操作次数最小
的方案的操作数
- 集合:所有把
- 状态计算以对
a中的第i个字母
操作不同划分
对于A中的前i个字母、B中的前j个字母:
1、添加。dp[i][j-1]
表示使得A中的前 i 个字母变成B中的前 j-1 个的操作次数,那么A再添加1个字母就变成B的前 j 个字母了dp[i][j] = dp[i][j-1]+1
2、删除。dp[i-1][j]
表示使得A中的前 i - 1个字母变成B中的前 j 个字母的操作次数,那么A删掉1个字母就变成B的前 j个字母了dp[i][j] = dp[i-1][j] + 1
3、替换。若a[i] != b[j]
,那么只要使得A中的前i-1个字母变成B中的前j-1个字母,dp[i][j] = dp[i-1][j-1] + 1
4、什么都不做。当a[i] == b[j]
, 有dp[i][j] = dp[i-1][j-1]
#include<iostream>
using namespace std;
const int N = 1010;
int d[N][N]; // d[i][j] 集合:所有把a中的前i个字母变成b的前j个字母的操作集合 属性:最小的操作次数
char A[N], B[N];
int main() {
int n, m;
cin >> n;
cin >> A + 1;
cin >> m;
cin >> B + 1;
for(int i = 0; i <= n;i++) d[i][0] = i; // 删除操作,删除i次
for(int i = 0; i <= m;i++) d[0][i] = i; // 添加操作,添加i次
for(int i = 1; i <= n;i++)
for(int j = 1; j <= m;j++) {
d[i][j] = min(d[i][j - 1],d[i-1][j]) + 1;
d[i][j] = min(d[i][j], d[i-1][j-1] + (A[i] != B[j]));
}
cout << d[n][m] << endl;
return 0;
}