Luogu P1020 导弹拦截
最多能拦截的导弹数为最长不上升子序列的长度
根据$dilworth$定理(偏序集所能划分成的最少的全序集的个数等于最大反链的元素个数)可以得到第二问的答案就是最长上升子序列的长度。
$ n \le 100000 $ 因此$O(n^{2})$的算法不能得全分,这里讲解$O(nlog{n})$的算法。
假设我们要求某序列的最长不上升子序列的长度。
我们设置两个数组,$a$与$c$,$a[i]$代表序列的第$i$个元素,$c[i]$表示长度为$i$的不上升子序列的最后一位的最大值。
再设置一个变量$len$,为$c$数组的长度。
从1到$n$扫描数组$a$
检查$a[i]$是否小于等于$c[len]$,如果是,则$c[++len] = a[i]$
如果否,那么寻找$c$中第一个小于$a[i]$的数,并将其替换成$a[i]$。
如果按照朴素的查找方法,寻找第一个小于$a[i]$的数所需时间复杂度为$O(n)$,这样算法整体复杂度仍为$O(n^2)$,我们需要对其进行优化。
我们会发现,因为我们找的是第一个小于$a[i]$的数,因此$c$数组是满足二分性的,可以使用二分查找。这样一次查找的时间复杂度就降为了$O(log{n})$,整体的时间复杂度为$O(nlog{n})$。
我们要尽可能让每一个$c[i]$尽量大,因为不下降子序列的最后一位越大,后面能选择的数就越多。
函数lower_bound upper_bound
lower_bound与upper_bound的作用就是二分查找,lower_bound可以找到第一个大于等于某个数的数,upper_bound可以找到第一个大于某个数的数,她们的返回值为查找到的数的指针,如需获得这个数的位置,可以用获得的指针减去数组开头的指针。如需获得这个数,直接设置一个指针存下来就行
$eg:$
我们要在数组$b$中找到第一个大于等于$x$的数$p$。($b$的长度为$m$)
*p = lower_bound(b,b + m,x);
则$*p$就是这个数
p = lower_bound(b,b + m,x) - b;
则$b[p]$就是这个数。
upper_bound的使用方法同上
注意,lower_bound和upper_bound只能在升序序列里使用。
那么我们想要在一个降序序列里找到第一个小于等于或小于$x$的数该怎么办呢?
我们可以修改一下lower_bound或upper_bound,加上$cmp$函数,或使用$greater<int>()$
$eg:$
*p = lower_bound(b,b + m,x,greater<int>); bool cmp(const int& a,const int& b) { return a > b; } *p = lower_bound(b,b + m,x,cmp);
$Code:$
#include <iostream> #include <cstdio> #include <algorithm> using namespace std; const int MAXN = 100010; int len1,len2,n; int a[MAXN],f1[MAXN],f2[MAXN]; int main() { while(cin >> a[++n]);n--; len1 = len2 = 1; f1[1] = f2[1] = a[1]; for(int i = 2; i <= n; i++) { if(a[i] <= f1[len1]) { f1[++len1] = a[i]; } else { int p = upper_bound(f1 + 1,f1 + len1 + 1,a[i],greater<int>()) - f1; f1[p] = a[i]; } if(a[i] > f2[len2]) { f2[++len2] = a[i]; } else { int p = lower_bound(f2 + 1,f2 + len2 + 1,a[i]) - f2; f2[p] = a[i]; } } cout << len1 << endl << len2 << endl; return 0; }