题目链接:AcWing1010.拦截导弹
1. 题目描述
某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。
但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。
某天,雷达捕捉到敌国的导弹来袭。
由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。
输入导弹依次飞来的高度(雷达给出的高度数据是不大于 30000 30000 30000 的正整数,导弹数不超过 1000 1000 1000 ),计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。
输入格式
共一行,输入导弹依次飞来的高度。
输出格式
第一行包含一个整数,表示最多能拦截的导弹数。
第二行包含一个整数,表示要拦截所有导弹最少要配备的系统数。
数据范围
雷达给出的高度数据是不大于
30000
30000
30000 的正整数,导弹数不超过
1000
1000
1000 。
输入样例:
389 207 155 300 299 170 158 65
输出样例:
6
2
2. 题目分析
- 条件1:第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。
- 目标:这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。
由条件1可知,“第一发炮弹可以选任意高度,后面的炮弹不能高于前一发的高度:”,说明我们要找一个下降子序列。
由目标可知,第一问是要找一个最长下降子序列,直接求反向的最长上升子序列即可,具体解法可见AcWing895_最长上升子序列。
对于第二问,有如下分析:
- 首先对于每一个数,我们可以将其接在一个现有的子序列之后,也可以创建一个新的子序列。
- 当我们将其接到一个子序列后面时,一定是所有满足要求子序列的最小的那一个(就是最后一个数是满足要求的最小的那一个)。
所以我们可以通过贪心法解决第二问。
贪心流程:
从前往后扫描每一个数,对于每个数:
- 如果现有子序列的结尾都小于当前数,则创建新的子序列。
- 将当前数放到结尾大于等于他的最小的子序列后面。
3. 代码实现
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int n, cnt;
int a[N];
int f[N], g[N];
int main()
{
while (~scanf("%d", &a[n])) n ++;
int res = 0;
for (int i = n - 1; i >= 0; i -- )
{
f[i] = 1;
for (int j = n - 1; j > i; j -- )
if (a[j] <= a[i])
f[i] = max(f[i], f[j] + 1);
res = max(res, f[i]);
}
printf("%d\n", res);
for (int i = 0; i < n; i ++ )
{
int k = 0;
while (k < cnt && g[k] < a[i]) k ++;
g[k] = a[i];
if (k >= cnt) cnt ++;
}
printf("%d\n", cnt);
return 0;
}
4. 时间复杂度
O ( n 2 ) O(n^2) O(n2)