标签:class mes main ++ 多少 描述 表示 数值 最大
在DP问题中,最长上升子序列(LIS)和最长公共子序列(LCS)无疑是最经典的入门题目,充分体现了DP的思想。
给定一个长度为N的数列,求数值严格单调递增的子序列的长度最长是多少。
input:
7
3 1 2 1 8 5 6
output:
4
(答案是:1 2 5 6)
首先解释一下什么叫做子序列,可以理解为在这个数列中从前往后挑数字组成数组,可以连着挑,可以跳着挑。
这个题目是典型的DP问题,首先我们先确定状态表示,集合f[i]表示的是以第i个数为结尾的上升子序列,属性是求Max。所以我们的f[i]表示的是:以第i个数字为结尾的上升子序列的长度的最大值。接下来考虑状态计算,我们先对集合进行划分,这一题的划分方式基本都是一个套路,就是以分割以前i-1个数字为结尾的上升子序列的长度的最大值的集合,比如说,以第1个数为结尾,第二个数为结尾。。。。。。,第i-1个数为结尾,集合中的每一个元素都+1,代表以第i个数字为结尾的上升子序列的长度的最大值。求出这个集合中的max即为答案。在这个集合中,有的数据是大于a[i]的,此时我们不更新它即可。
//数据范围 1≤N≤1000
#include <iostream>
using namespace std;
const int N = 1010;
int f[N];
int a[N];
int main() {
int n = 0;
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[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的子序列的字符串长度最长是多少。
input:
A:a c b d
B:a b e d c
output:
3
(a b d)
这也是个典型的DP问题,但是思路比LIS更难想。首先我们确定状态表示,集合f[i,j]表示所有由A字符串的前i个字母和B字符串的前j个字母构成的公共子序列的合集,属性是求Max,所以f[i,j]代表所有由A字符串的前i个字母和B字符串的前j个字母构成的公共子序列的长度的最大值。接下来考虑状态计算,这种类型的题目集合划分的思路更难想到,记住就好了。我们将集合f[i,j]先划分成四个区域,分别是第一种情况,不选A字符串第i个字母,不选B字符串第j个字母,第二种情况,不选择A字符串第i个字母,选择B字符串的第j个字母,第三种情况,选择A字符串的第i个字母,不选择B字符串的第j个字母,第四种情况,选择A字符串的第i个字母,选择B字符串的第j个字母。下面我们来逐一分析
两个字母都选的话,是需要特判的,两个字母相同时才考虑这种情况
f[i-1,j]和f[i,j-1]两种情况,我们需要明白f[i,j]代表的是所有由A字符串的前i个字母和B字符串的前j个字母构成的公共子序列的合集,这个合集中包含了许多情况,有些字母并不符合要求也不更新,但是它一定包含a[i]出现,b[i]也出现的情况,所以第一种情况包含在了第二,三种情况中。这代表着四种情况会有重合的,但是我们的属性是求max,所以效果都是一样的。
#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;
scanf("%s%s",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];
return 0;
}
总结
DP问题就按照考虑状态表示和状态计算的套路来做,想好状态转移方程。DP类型的题目没有一个固定的模版,还是要多积累,多总结,根据DP问题的性质来思考状态转移方程。
标签:class mes main ++ 多少 描述 表示 数值 最大
原文地址:https://www.cnblogs.com/mgd666/p/13166955.html