[NOIP1999 普及组] 导弹拦截
题目描述
某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。
输入导弹依次飞来的高度,计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。
输入格式
一行,若干个整数,中间由空格隔开。
输出格式
两行,每行一个整数,第一个数字表示这套系统最多能拦截多少导弹,第二个数字表示如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。
样例 #1
样例输入 #1
389 207 155 300 299 170 158 65
样例输出 #1
6
2
提示
对于前
50
%
50\%
50% 数据(NOIP 原题数据),满足导弹的个数不超过
1
0
4
10^4
104 个。该部分数据总分共
100
100
100 分。可使用
O
(
n
2
)
\mathcal O(n^2)
O(n2) 做法通过。
对于后
50
%
50\%
50% 的数据,满足导弹的个数不超过
1
0
5
10^5
105 个。该部分数据总分也为
100
100
100 分。请使用
O
(
n
log
n
)
\mathcal O(n\log n)
O(nlogn) 做法通过。
对于全部数据,满足导弹的高度为正整数,且不超过 5 × 1 0 4 5\times 10^4 5×104。
此外本题开启 spj,每点两问,按问给分。
upd 2022.8.24 \text{upd 2022.8.24} upd 2022.8.24:新增加一组 Hack 数据。
import java.io.*;
public class Main {
static int N = 100010;
static int q[] = new int[N], g[] = new int[N]; // g[] 存储当前现有的子序列的最后一个数
static int f[] = new int[N];
static int n;
public static void main(String[] args) throws IOException{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String tmp[] = br.readLine().split(" ");
n = tmp.length;
for(int i = 0; i < n; i++) q[i] = Integer.parseInt(tmp[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;
// 首先,这是一个单调不增子序列
// q[i]是待插入的数,f[mid]是当前位置的数
// 如果q[i]小于等于当前位置的数,说明当前位置可能取小了
// 否则当前位置一定取大了
// 这里的当前位置的数即长度为i子序列的最后一个最大的数
if(f[mid] >= q[i]) l = mid; // 可能取小了
else r = mid - 1; // 一定取大了
}
len = Math.max(len, r + 1); // 每次记录一下最长满足条件的子序列的长度
f[r + 1] = q[i]; // 将待插入的数插入到它应该的位置
}
System.out.println(len);
// 贪心策略:
// 从前往后扫描每个数,对于每个数:
// 1.如果当前子序列的结尾都都小于当前数,则创建新子序列
// 2.否则将当前数放到结尾大于等于它的最小的子序列的后面
int cnt = 0; // 表示当前子序列的个数
for(int i = 0; i < n; i++) {
int k = 0; // 从前往后找的序列
while(k < cnt && g[k] < q[i]) k++; // 如果没有遍历完当前序列并且当前序列的结尾小于当前数,就继续往后找
g[k] = q[i]; // 不管有没有满足条件的序列,都把q[i]加到g[]中
if(k >= cnt) cnt++; // 没有任何一个序列大于当前数,就新开一个序列存储当前数
}
System.out.println(cnt);
br.close();
}
}