线性动态规划
数字三角形
- 描述
给定一个如下图所示的数字三角形,从顶部出发,在每一结点可以选择移动至其左下方的结点或移动至其右下方的结点,一直走到底层,要求找出一条路径,使路径上的数字的和最大。
- 分析
- 代码
#include<bits/stdc++.h>
using namespace std;
const int N = 510,INF = -1e9;
int f[N][N],s[N][N];
int n;
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
scanf("%d",&s[i][j]);
for(int i=0;i<n+10;i++)//初始化多初始化一点,边界是从边界外更新的。
for(int j=0;j<i+10;j++)
f[i][j] = INF;
f[1][1] = s[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]+s[i][j],f[i-1][j]+s[i][j]);
int res = INF;
//最后只是走到了最后一层,所以需要从最后一层中找到最大值。
for(int i=1;i<=n;i++)res = max(res,f[n][i]);
printf("%d",res);
return 0;
}
最长上升子序列
- 描述
给定一个长度为 N 的数列,求数值严格单调递增的子序列的长度最长是多少。
注意不是连续的子序列,只要是子序列就行。
- 分析
- 代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
int f[N],a[N];
int n;
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
for(int i=1;i<=n;i++){
f[i] = 1;//只有a[i]一个数时为1
for(int j=1;j<i;j++){
if(a[j]<a[i])f[i] = max(f[j]+1,f[i]);
}
}
int res = 0;
//最终的最大值不确定以第几个数结尾,所以遍历寻找。
for(int i=1;i<=n;i++)res = max(res,f[i]);
printf("%d",res);
return 0;
}
最长公共子序列
- 描述
给定两个长度分别为 N 和 M 的字符串 A 和 B,求既是 A 的子序列又是 B 的子序列的字符串长度最长是多少。
-
分析
时间复杂度为O(n2) -
代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
char a[N],b[N];
int f[N][N];
int n,m;
int main()
{
scanf("%d%d",&n,&m);
scanf("%s%s",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][j-1],f[i-1][j]);
}
printf("%d",f[n][m]);
return 0;
}
最短编辑距离
- 描述
给定两个字符串 A 和 B,现在要将 A 经过若干操作变为 B,可进行的操作有:
删除–将字符串 A 中的某个字符删除。
插入–在字符串 A 的某个位置插入某个字符。
替换–将字符串 A 中的某个字符替换为另一个字符。
现在请你求出,将 A 变为 B 至少需要进行多少次操作。
-
分析
-
代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
char a[N],b[N];
int f[N][N];
int n,m;
int main()
{
scanf("%d",&n);
scanf("%s",a+1);
scanf("%d",&m);
scanf("%s",b+1);
//初始化
//a中0个字符变成b需要i次插入。
//a变成b中的0个字符需要i次删除。
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);
}
}
printf("%d",f[n][m]);
return 0;
}
区间DP
石子合并
- 描述
设有 N 堆石子排成一排,其编号为 1,2,3,…,N。
每堆石子有一定的质量,可以用一个整数来描述,现在要将这 N 堆石子合并成为一堆。
每次只能合并相邻的两堆,合并的代价为这两堆石子的质量之和,合并后与这两堆石子相邻的石子将和新堆相邻,合并时由于选择的顺序不同,合并的总代价也不相同。
例如有 4 堆石子分别为 1 3 5 2, 我们可以先合并 1、2 堆,代价为 4,得到 4 5 2, 又合并 1,2 堆,代价为 9,得到 9 2 ,再合并得到 11,总代价为 4+9+11=24;
如果第二步是先合并 2,3 堆,则代价为 7,得到 4 7,最后一次合并代价为 11,总代价为 4+7+11=22。
问题是:找出一种合理的方法,使总的代价最小,输出最小代价。
-
分析
由于需要用到i到j的数量和,要用前缀和。时间复杂度是O(n3) -
代码
#include<bits/stdc++.h>
using namespace std;
const int N = 310;
int f[N][N],a,s[N];
int n;
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a);
s[i] = s[i-1]+a;
}
for(int len=2;len<=n;len++){
for(int i=1;i+len-1<=n;i++){
int l = i,r = i+len-1;
f[l][r] = 1e9+10;//每次f[l][r]需要初始化为一个较大的数。
for(int k=l;k<r;k++){
f[l][r] = min(f[l][r],f[l][k]+f[k+1][r]+s[r]-s[l-1]);
}
}
}
printf("%d",f[1][n]);
return 0;
}