1. 问题描述:
某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。输入导弹依次飞来的高度(雷达给出的高度数据是不大于30000的正整数,导弹数不超过1000),计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。
输入格式
共一行,输入导弹依次飞来的高度。
输出格式
第一行包含一个整数,表示最多能拦截的导弹数。第二行包含一个整数,表示要拦截所有导弹最少要配备的系统数。
数据范围
雷达给出的高度数据是不大于 30000 的正整数,导弹数不超过 1000。
输入样例:
389 207 155 300 299 170 158 65
输出样例:
6
2
来源:https://www.acwing.com/problem/content/1012/
2. 思路分析:
分析题目可以知道输出的第一个结果比较好解决,我们求解出最长不上升子序列的长度即可,与最长上升子序列的求解是一样的只是在求解的时候判断的条件不一样而已,最长上升子序列的判断条件为大于,最长不上升子序列是小于等于;所以主要是解决第二个问题,我们需要求解出至少需要多少个不上升子序列可以将所有的数字覆盖掉,这里其实使用到了贪心的思想,贪心一般先从直觉上想看哪一种解决方案可能更好,然后证明这种方法是否正确,我们可以从前往后扫描每一个数字,对于每一个数字主要是分为两种情况:
- 如果现有的子序列的结尾都小于当前的数字则需要创建新的子序列,计数加1
- 如果当前的数字是位于之前子序列的之中的,我们将当前的数字放到大于等于当前数最小的那个数的位置上
为什么这样做是正确的呢?其实证明出当前贪心解等于最优解即可,也即证明两个数字是相等的,设A为贪心解得到的序列个数,B为最优解对应的子序列个数,现在我们需要证明A = B,证明贪心的一种常用方法是调整法,先要证明A = B,则证明两个方向即可,一个是A >= B,另外一个是A <= B,因为求解的是最少的个数,所以贪心解的个数一定小于最优解的个数,也即A >= B,所以核心是证明A <= B,我们可以找到贪心解与最优解不一样的地方,然后将最优解不一样的数字调整到与贪心解一样可以发现调整之后还是满足要求的,而且不会增加子序列的个数,所以A <= B,所以贪心解 = 最优解。具体的实现:我们需要借助于一个数组g,枚举每一个数字模拟上面说到的两种情况,如果不能够接在所有子序列的后面说明需要创建一个新的子序列,如果可以接在某一个子序列的后面那么我们找到子序列中第一个大于等于当前数字的位置,然后将这个位置的值覆盖为当前的数字即可,可以发现g数组的每一个位置代表一个新的序列,并且可以发现当前求解的问题与最长上升子序列的贪心思路求解的过程是一样的,结果都是最长上升子序列的长度。
3. 代码如下:
if __name__ == '__main__':
# 输入的导弹高度
a = list(map(int, input().split()))
n = len(a)
dp = [0] * n
res = 0
for i in range(n):
dp[i] = 1
for j in range(i):
if a[i] <= a[j]:
dp[i] = max(dp[i], dp[j] + 1)
res = max(res, dp[i])
print(res)
g = [0] * n
count = 0
for i in range(n):
k = 0
while k < count and g[k] < a[i]: k += 1
g[k] = a[i]
if k >= count: count += 1
print(count)