最长上升子序列II
题目描述
给定一个长度为 N 的数列,求数值严格单调递增的子序列的长度最长是多少。
- 输入格式
第一行包含整数 N 。
第二行包含 N 个整数,表示完整序列。
- 输出格式
输出一个整数,表示最大长度。
- 数据范围
- 输入样例:
7
3 1 2 1 8 5 6
- 输出样例:
4
题解
此数据经过加强所以不能用传统的动态规划的方法去做,时间复杂度为 会超时
这里采用 贪心 + 二分
- 贪心思想:用样例来举例,遍历到第二个数 1,我们有两个长度为 1 的最长上升子序列,但是后续的数字如果能接到 3 的后面那么就一定可以接到 1 的后面,所以就没有必要存储数字 3 了 ,所以我们只需存储每一个上升子序列结尾最小的数字,并且维护这样的一个数组 。
并且这个数组是严格单调递增的,证明如下:
证明: 不妨设长度为 5 的上升子序列(简称x)结尾为 a ,长度为 6 的上升子序列(简称y)结尾为 b ,如果 a > b, 那么 那么x5 > y[5], 那么就存在一个长度为 5 的上升子序列,他的结尾比 a 小,所以就跟上面的命题矛盾。
所以我们每次可以去比当前数值小的最大值,并且将这个值放在这个最大值的后面,并且数组单调递增,可以考虑用二分 (PS:并不是一定要单调才能用二分)
- 具体步骤
- 维护一个数组 f[N],用来存放每个上升子序列的 最小 结尾值
- 遍历数组 f ,利用二分查找小于当前值的的最大值 f[i], l = 0, r = len 初试为 0,为这个f 的长度:
- 若找到: 则覆盖 f[i+1]的位置;
- 若找不到:扩充数组,其实还是放在 f[i+1] 的位置,但是这里要扩充len;
- 不管找到找不到,二分查找都会返回一个结果;
代码
#include <iostream>
using namespace std;
const int N = 100010;
int a[N], p[N];
int main() {
int n;
cin >> n;
for(int i = 0; i < n; i++) cin >> a[i];
int len = 0;
for(int i = 0; i < n; i++) {
int l = 0, r = len; // 初始值
while(l < r) {
int mid = (l + r + 1) >> 1;
if(p[mid] >= a[i]) r = mid - 1;
else l = mid;
}
p[l + 1] = a[i];
len = max(len, l + 1); // 扩充数组的长度
}
cout << len << endl;
return 0;
}
拦截导弹
题目描述
某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。
但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。
某天,雷达捕捉到敌国的导弹来袭。
由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。
输入导弹依次飞来的高度(雷达给出的高度数据是不大于30000的正整数,导弹数不超过1000),计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。
- 输入格式
共一行,输入导弹依次飞来的高度。
- 输出格式
第一行包含一个整数,表示最多能拦截的导弹数。
第二行包含一个整数,表示要拦截所有导弹最少要配备的系统数。
- 数据范围
雷达给出的高度数据是不大于 30000 的正整数,导弹数不超过 1000。
- 输入样例:
389 207 155 300 299 170 158 65
- 输出样例:
6
2
题解
第一问这里就不赘述了,通过动态规划和上面的贪心+二分都可以解决
这里主要来分析第二问
贪心思想:用样例来举例,389加入系统一,从207开始,我们肯定是希望每个系统的结尾的值越大越好,这样每个系统可以拦截更多的导弹,并且每个系统的值都是递减的,所以我们要查找大于当前值的最小值,并且将其覆盖;如果都小于当前值则只能增加一个新的系统。
上一题是上升子序列,然后存储每一个子序列结尾的最小值,这道题是一个下降子序列,使结尾的值尽可能的打,但都是去 查找并替换大于当前值的最小值,最后输出维护的这个数组。所以两个代码是一样的。
代码
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int a[N], f[N], g[N];
int n;
int main() {
int res = 0;
while(cin >> a[n]) n++;
for(int i = 0; i < n; i++) {
f[i] = 1;
for(int j = 0; j < i; j++) {
if(a[j] >= a[i])
f[i] = max(f[i], f[j] + 1);
}
res = max(res, f[i]);
}
cout << res << endl;
int len = 0;
for(int i = 0; i < n; i++){
int l = 0, r = len;
while(l < r){
int mid = (l + r + 1) >> 1;
if(g[mid] >= a[i]) r = mid - 1;
else l = mid;
}
g[l + 1] = a[i];
len = max(len, l + 1);
}
cout << len << endl;
return 0;
}