导弹拦截
题目描述
某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。
输入导弹依次飞来的高度(雷达给出的高度数据是 ≤ 50000 ≤50000 ≤50000的正整数),计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。
输入输出格式
输入格式:
1行,若干个整数(个数 ≤ 100000 ≤100000 ≤100000)
输出格式:
2行,每行一个整数,第一个数字表示这套系统最多能拦截多少导弹,第二个数字表示如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。
输入输出样例
输入样例#1:
389 207 155 300 299 170 158 65
输出样例#1:
6
2
说明
为了让大家更好地测试n方算法,本题开启spj,n方100分,nlogn200分
每点两问,按问给分
思路
可以自己手推一遍吧,可以发现第一问的是最长的不上升子序列;第二问求的是最长的上升子序列。
那么就很容易想到一个
O
(
n
2
)
O(n^2)
O(n2)的算法,动态规划转移。(就都是
L
I
S
LIS
LIS的板子,不过是大于小于和等于的判断要注意一下)
那么我们就要开始想一个优化,因为 O ( n 2 ) O(n^2) O(n2)的过不了,所以下一步想的应该是 O ( n l o g n ) O(nlogn) O(nlogn)的算法,在这里介绍一个 S T L STL STL的神奇的算法哦~~(STL大法好)~~
Q:为什么这个可以采用 S T L STL STL优化?
A:首先可以看出来是一个求最长的不上升子序列和最长的上升子序列,之前我们是用O(n^2)的算法仅仅考虑序列的长度,但是我们还可以换一个思路,可以考虑去构造一个最长的不上升子序列和最长的上升子序列。试着枚举每一个数字求出这一个序列,而且在这里求出来的序列都是有顺序的,对于顺序的查找很自然的就可以想到lower_bound和upper_bound二分查找一个序列的位置。
难点:这里的upper_bound()和lower_bound()的用法
1.两个函数作用的一定是一个有序的数列;
Q:这里的有序指的是相对于谁有序呢?
A:它是对于比较器有序,并且必须是升序!
Q:如何在一个 降序的序列 中使用?
A:我们可以把比较器的“<”改成“>”,这里有两种方法:
- 类似于排序的sort函数中的bool函数一样,在这里我们也可以写一个bool函数。
bool mycmp(const int&a,const int&b)
{
return a>b;
}
- 当然,你还可以使用c+中的内置函数: g r e a t e r < i n t > ( ) greater<int>( ) greater<int>()就是c++友情提供的方便的大于函数,这样就不用自己动手写一个bool函数了
2.二个函数查找的东西不一样:
分清楚他们的用法:
lower_bound()函数查找的是一个大于等于自己的一个数字;
upper_bound()函数查找的是一个大于自己的数字;
没错这俩就差了一个
=
=
=。
3.两个函数的实现方法是二分查找,复杂度和二分是一样的 O ( n l o g n ) O(nlogn) O(nlogn)
4.注意格式:lower_bound(a+1,a+1+n,x)-a; upper_bound(a+1,a+1+n,x)-a;
Q:为什么后面要有“-a”这个操作?
A:因为lower_bound()和upper_bound()的返回值都是指针, i n t ∗ p int *p int∗p=
lower_bound(a+1,a+1+n,x),那么*p就是要找的y,也就是具体的数值。 那么我们用这个指针减去数组开头的指针(即数组名),得到两个指针的差,就是数组下标。因为不喜欢使用指针,所以有“-a”的操作。
回归题意:
- 第一问要求的是一个最长不上升子序列,所以要求的序列应该是不严格单调递减的,对于一个下降的序列的函数的处理,上面有;
- 第二问是要求一个最长的严格上升的单调序列,这个就正常处理。
可以模拟一遍求序列的过程【这里面是证明这样做的道理】:
- 建了两个数组存储要求的序列 ( d 1 , d 2 ) (d1,d2) (d1,d2),最后序列的长度就是 a n s w e r ( l e n 1 , l e n 2 ) answer(len1,len2) answer(len1,len2);
- 求最长的不上升子序列:对于当前要比较的数字 a [ i ] a[i] a[i],若是小于等于 d 1 [ l e n ] , d1[len], d1[len],那么就把 a [ i ] a[i] a[i]接在 d 1 [ l e n ] d1[len] d1[len]的后面,反之,就用upper_bound(这里就用到了上面的greater<int>,把upper_bound反转。)找到第一个小于自己的数字,把这个数字替换【因为我们想要序列尽可能的长,就需要在序列里的数字尽可能的大,这样子的话,就会有更多可能的数字接在序列的后面】。
- 同理可得:求最长的上升子序列:对于当前要比较的数字 a [ i ] a[i] a[i],若是大于 d 2 [ l e n ] d2[len] d2[len],那么就把 a [ i ] a[i] a[i]接在 d 2 [ l e n ] d2[len] d2[len]的后面,反之,就用lower_bound找到第一个大于等于自己的数字,把这个数字替换【因为我们想要序列尽可能的长,就需要在序列里的数字尽可能的小,这样子的话,就会有更多可能的数字接在序列的后面】。
code
1. O ( n 2 ) O(n^2) O(n2)的做法
#include<bits/stdc++.h>
using namespace std;
int n;
int a[210];
int f[210],g[210];
int ans1,ans2;
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;++i) scanf("%d",&a[i]);
for(int i=1;i<=n;++i)
{
for(int j=1;j<i;++j)
if(a[i]<=a[j])
f[i]=max(f[i],f[j]);
f[i]+=1; ans1=max(ans1,f[i]);
for(int j=1;j<i;++j)
if(a[i]>a[j])
g[i]=max(g[i],g[j]);
g[i]+=1; ans2=max(ans2,g[i]);
}
printf("%d\n%d",ans1,ans2);
return 0;
}
2. O ( n l o g n ) O(nlogn) O(nlogn)的做法
#include<bits/stdc++.h>
using namespace std;
const int nn=100010;
int n;
int a[nn];
int d1[nn],d2[nn];
int len1,len2;
int main()
{
while(cin>>a[++n]);
n--;
d1[++len1]=d2[++len2]=a[1];
for(int i=2;i<=n;++i)
{
if(a[i]<=d1[len1]) d1[++len1]=a[i];
else
{
int p=upper_bound(d1+1,d1+1+len1,a[i],greater<int>())-d1;//求最长的不上升子序列
d1[p]=a[i];
}
if(a[i]>d2[len2]) d2[++len2]=a[i];
else
{
int p=lower_bound(d2+1,d2+1+len2,a[i])-d2;//求最长的上升子序列
d2[p]=a[i];
}
}
printf("%d\n%d",len1,len2);
return 0;
}
二分求(非严格)上升和(非严格)下降
上面我们讲述了一下严格上升子序列以及非严格单调下降子序列的求法。
接下来,我要分享一下,如何求严格上升子序列以及非严格单调上升子序列的求法,如果要求另外两个的话可以使用reverse
函数,把数组翻转。
-
严格上升子序列:在构造一个严格上升子序列 b b b的时候,我们可以使用
lower_bound()
把序列尽可能的小,这样子的话,就可以把更多的大于最后一个数字的数字加到 b b b数组的后面。这样子就是使用贪心的思想求出最长的严格上升子序列。 -
非严格单调上升子序列:与上面的道理基本相同,不过就是把
lower_bound()
换成是upper_bound()
函数。
思考一下,为什么我们要用这两个函数?换句话说,这两个函数可以互换吗?
当然是不可以(博主因为这个错了好多次……)。
对于严格上升子序列而言,如果,我们使用upper_bound()
的话,我们就会把相等的数字略过,直接找到大于自己的数字,之后把自己加入到序列的某一个位置,但是前面的和自己相等的数字还是会保留在
b
b
b序列里,所以,不可以互换。
对于 非严格单调上升子序列 ,也是一样的道理。(自证)
尾生抱柱,至死方休。