洛谷p1020
前言
这道题虽然我在很久之前已经在别的地方做过了,但现在看到这道题,还是觉得有一些不得不提的知识盲区。比如一直读入到没输入,lower_bound的用法等等。总之,想写一点自己的体会。
题目大意
给出一些入侵导弹的高度(不知道数量),我们要发射拦截导弹到与侵入导弹的高度相等才能拦截掉这一颗导弹,并且每套系统每次发射的导弹的高度都不能超过前一颗,问一套系统最多能拦截几颗导弹,拦截所有导弹最少需要多少套系统。
题目分析
对于第一问,显然是求最长不上升子序列。第二问可能有点难,据说根据Dilworth定理,把一个数列划分为最少的最长不上升序列的数目等于最长上升子序列的长度(这个定理超出了我的理解范围)。我想用我的方法解释一下为什么是求最长上升子序列。首先,考虑这样一个数列:3 5 4 2 6 6。打掉3,需要一套系统;打掉5,需要第2套系统。我们可以用c[i]表示第i套系统打掉的最后一颗导弹的高度。则c[1]=3,c[2]=5。考虑4,只能被第二套系统打掉,而被第二套系统打掉肯定比使用第三套系统优,所以更新c[2]=4。现在考虑2,既能被第一套系统打掉,又能被第二套系统打掉,但显然被第一套系统打掉更优,所以更新c[1]=2。最后考虑第一个6,它不能被前两套系统打掉,所以需要第三套系统,c[3]=6。考虑最后一个6,它能被第三套系统打掉,所以不需要多用一套系统。思考可知,c数组构成了一个单调上升的序列(若不是,则前一个系统可以打掉这颗导弹,就不需要多用一个系统了),而且是原数列中最长的上升子序列,由此,命题得证。反之,最长上升子序列也是用上述方法求的,维护c数组,使之保持是一个单调上升序列,每次做到一个新的数,可以二分查找这个数在c数组中位置,然后更新。这样复杂度就是O(nlogn)。
详细看代码:
#include<iostream>
#include<algorithm>
using namespace std;
int a[100005],b[100005],c[100005],t1,t2,n;//b数组用来求第一问的最长不上升子序列,c数组用来求第二问的最长上升子序列
bool cmp(int x,int y)
{
return x>y;
}
int main()
{
int i=1;
while (cin>>a[i]) i++;//读入导弹高度,若有东西读,则cin的返回值为true,不然返回值为false
n=i-1;//i多加了一次,n表示导弹的数量
// cin>>n;
// for (int i=1;i<=n;i++) cin>>a[i];
t1=t2=1;
b[1]=c[1]=a[1];//这样就不用赋初值了
for (int i=2;i<=n;i++)
{
int x;//x表示a[i]应该插入的位置
if (a[i]<=b[t1]) b[++t1]=a[i];
else
{
x=upper_bound(b+1,b+t1+1,a[i],cmp)-b;//找到第一个严格小于这个数的位置
b[x]=a[i];//更新
}
if (a[i]>c[t2]) c[++t2]=a[i];
else
{
x=lower_bound(c+1,c+t2+1,a[i])-c;//找到第一个大于等于这个数的位置
c[x]=a[i];//更新
}
}
cout<<t1<<'\n'<<t2;
}
一些细节
读入
此题要求读到文件结束,可以用cin,若读得到,则返回值为true,不然是false。也可以用scanf:
while (scanf("%d",&a[++n]));
或者
while (scanf("%d",&a[++n])!=EOF);
二分查找
我的二分是用STL库里的lower_bound和upper_bound实现的。这两个函数都是对于单调递增的序列来说的,如果要对单调递减的序列使用的话,可以加一个cmp比较函数(一定要写大于号,实现看代码),优先级反转,就可以认为是一个单调递增序列了。lower_bound(a+1,a+n+1,x)-a表示a数组里第一个大于等于x的数的位置, upper_bound(a+1,a+n+1,x)-a就表示a数组里第一个大于x的数的位置。单调递减序列要格外注意。初学者还需要多加思考与练习。
单调队列
我所使用的单调队列复杂度为O(nlogn),因为是对于每个数进行二分查找位置,然后更新。当然,还有O( n2 n 2 )的算法,写起来比较简洁。
总结
这道题其实并不难,不过它需要你对单调队列的深刻理解。