时间:2021/2/22
1. 最大子段和
对于全是非正数的序列,答案是其中元素的最大值
对于有正数的序列,考虑以每一个点位结尾的最大子段和,这个子段一定满足其前缀和均非负,因为如果有一个前缀是负的,那么减掉这个前缀对于这个点一定更优,并且这个子段要尽量往前延伸
2. 最长上升子序列 (Longest Increasing Subsequence)
子序列长度越长,结尾元素最小值(记为min)越大
-
从队列中的第1个数开始枚举,第1个元素作为长度为1的子序列结尾min,存在q[1]中
-
第二个元素与q比较,插入到单增数列q[x]中,作为长度为x的子序列结尾min
···
在这个过程中,如果枚举到一个小数,会更新某个长度的结尾元素最小值
如:
- q [1]2 [2]6 [3]11
- 下一个数是3
- 则更新成q [1]2 [2]3 [3]11 ,
因为显然在2的长度下以3结尾比以6结尾具有更大的上升空间
如果枚举到一个大数,一个更大的长度产生并以此数结尾
最后的数组q就是最大上升子序列的一个答案(答案可能不唯一),q的长度就是最大上升子序列的长度
3. 最长公共子序列
输入样例:序列a: acbd 序列b: abedc
枚举到 a 的第 i 个字符和b的第 j 个字符时,f[ i ][ j ]存放最大值,集合划分:i j 选或不选共四种情况,f[ i ][ j ]最终取这四种情况的最大值
1 不选 i 不选 j f[i][j] = f[i - 1][j - 1]
2 不选 i 选 j f[i][j] = f[i - 1][j]
其实f[i - 1][ j ] 真正表示的是: 不选 i , 可能选 j , 也可能不选 j , 由于这里是求最大值,即使真的是不选 i 不选 j 的时候取到最大值,最终结果也还是正确的
3 选 i 不选 j f[i][j] = f[i][j - 1]
4 选 i 选 j f[i][j] = f[i - 1][j - 1] + 1
最后,这四种情况的f[ i ][ j ]取最大值,得新的f[ i ][ j ]
其实,情况 2 3 已经包含了情况 1,所以最终的f[ i ][ j ]只需看情况 2 3 4 的最大值,重新整理一遍思路:
集合分为三部分(1 2 部分有重合)
1 不选 i ,选 j 或 不选 i 不选 j f[i][j] = f[i - 1][j]
2 选 i 不选 j 或不选 i 不选 j f[i][j] = f[i][j - 1]
3 选 i 选 j f[i][j] = f[i - 1][j - 1] + 1
(判断条件:a[i] == b[j])
取 1 2 3 三种情况下f[i][j]的最大值,得真正的f[ i ][ j ]
伪代码:
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ ){
if (a[i] == b[j])
f[i][j] = max(f[i - 1][j], f[i][j - 1], f[i - 1][j - 1] + 1);
else f[i][j] = max(f[i - 1][j], f[i][j - 1]);
}
但是这个算法的数据范围是1000,时间复杂度是O( n 2 n^2 n2) , 对于数据范围更大的题目该如何转换思路?
- 问题描述
给出两个长度为 n 的整数序列,求它们的最长公共子序列(LCS)的长度,保证第一个序列中所有元素都不重复。
注意:
第一个序列中的所有元素均不重复。
第二个序列中可能有重复元素。
一个序列中的某些元素可能不在另一个序列中出现。
- 输入格式
第一行包含一个整数 n。
接下来两行,每行包含 n 个整数,表示一个整数序列。
- 输出格式
输出一个整数,表示最长公共子序列的长度。
- 数据范围
1≤n≤106,
序列内元素取值范围 [1,106]。
- 输入样例1:
5
1 2 3 4 5
1 2 3 4 5
- 输出样例1:
5
- 输入样例2:
5
1 2 3 5 4
1 2 3 4 5
- 输出样例2:
4
最长公共子序列和最长上升子序列可以相互转化,转化的关键是其中有一个序列没有重复元素
#include <iostream>
#include <cstdio>
using namespace std;
int n;
const int N = 1000010;
int bb[N]<