算法学习系列(三十三):线性DP
目录
- 引言
- 一、数字三角形
- 二、最长上升子序列
- 三、最长公共子序列
引言
这个线性DP其实也就是一种描述吧,有的是一维、二维、多维的,就是这个动规方程是按顺序来的,所以叫做线性,然后还是得按题目来看,把每种题都见过才能有思路,才会写,DP其实没啥思想规范,就是做题见题,才会做题。
一、数字三角形
思路:
把这个三角形看成二维的,横着的代表行,斜着的代表列,则
f
[
i
]
[
j
]
=
m
a
x
(
f
[
i
−
1
]
[
j
−
1
]
,
f
[
i
−
1
]
[
j
]
)
+
a
[
i
]
[
j
]
f[i][j]=max(f[i-1][j-1],f[i-1][j])+a[i][j]
f[i][j]=max(f[i−1][j−1],f[i−1][j])+a[i][j]
题目描述:
给定一个如下图所示的数字三角形,从顶部出发,在每一结点可以选择移动至其左下方的结点或移动至其右下方的结点,一直走到底层,
要求找出一条路径,使路径上的数字的和最大。
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
输入格式
第一行包含整数 n,表示数字三角形的层数。
接下来 n 行,每行包含若干整数,其中第 i 行表示数字三角形第 i 层包含的整数。
输出格式
输出一个整数,表示最大的路径数字和。
数据范围
1≤n≤500,−10000≤三角形中的整数≤10000
输入样例:
5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
输出样例:
30
示例代码:
#include <cstdio>
#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 <= n; ++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], 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 << endl;
return 0;
}
二、最长上升子序列
思路:
f
[
i
]
f[i]
f[i] 代表以
i
i
i 为结尾的最长递增子序列最长长度,每个
i
i
i 中,判断之前小于
a
[
i
]
a[i]
a[i] 的中的最长是多少,在加上自己,如果没有不小于
a
[
i
]
a[i]
a[i] 的,则
f
[
i
]
f[i]
f[i] 就为1(只有自己)
题目描述:
给定一个长度为 N 的数列,求数值严格单调递增的子序列的长度最长是多少。
输入格式
第一行包含整数 N。
第二行包含 N 个整数,表示完整序列。
输出格式
输出一个整数,表示最大长度。
数据范围
1≤N≤1000,−109≤数列中的数≤109
输入样例:
7
3 1 2 1 8 5 6
输出样例:
4
示例代码:
#include <cstdio>
#include <iostream>
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 = 1; j < i; ++j)
{
if(a[i] > a[j]) 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;
}
三、最长公共子序列
思路:
f[i][j]代表A串前i个字母和B串前j个字母的最长序列,f[i][j]可以划分为a[i] b[j]都不选,当前选a[i]不选b[j]中的,选b[j]不选a[i],两个都选,也就是f[i-1][j-1] f[i][j-1] f[i-1][j] f[i-1][j-1]+1,但是选A串中的不选B中的,其实不等于f[i][j-1],这只说明前i个和前j-1个中的最长,没有说一定选a[i],但因为我们求的是最大值,而f[i][j-1] 一定是包含要的这种情况的,而且f[i][j-1]的最大情况也就是选a[i]不选b[j]这种情况的,所以虽然不等于但求最大值结果是一样的,f[i-1][j]也是等价的,同理f[i-1][j] 也是包含f[i-1][j-1]这种情况的,所以从三个里面取最大值就行了。
题目描述:
给定两个长度分别为 N 和 M 的字符串 A 和 B,求既是 A 的子序列又是 B 的子序列的字符串长度最长是多少。
输入格式
第一行包含两个整数 N 和 M。
第二行包含一个长度为 N 的字符串,表示字符串 A。
第三行包含一个长度为 M 的字符串,表示字符串 B。
字符串均由小写字母构成。
输出格式
输出一个整数,表示最大长度。
数据范围
1≤N,M≤1000
输入样例:
4 5
acbd
abedc
输出样例:
3
示例代码:
#include <cstdio>
#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;
cin >> a+1 >> b+1;
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] << endl;
return 0;
}