一、参考链接:
1、算法部分:http://www.cnblogs.com/itlqs/p/5743114.html
2、二分搜索upper_bound and lower_bound:https://blog.csdn.net/qq_40160605/article/details/80150252
二、例题:
洛谷:P1020导弹拦截:https://www.luogu.org/problemnew/solution/P1020
题目大意:求最长不上升子序列与不上升子序列个数
思路分析:
1、最长不上升子序列的朴素dp为O(n2)级别,对于10W这样的大数据还是处理不了,于是需要更为优秀的O(nlogn)级别的算法。
具体思路如下:使用数组d来为维护子序列,d[i]表示:长度为i的子序列最后一位的最大值
考虑新进来一个元素a[i]:
如果这个元素大于等于d[len],直接让d[len+1]=a[i],然后len++。这个很好理解,当前最长的长度变成了len+1,而且d数组也添加了一个元素。
如果这个元素小于d[len]呢?说明它不能接在最后一个后面了。那我们就看一下它该接在谁后面。
准确的说,并不是接在谁后面。而是替换掉谁。因为它接在前面的谁后面都是没有意义的,再接也超不过最长的len,所以是替换掉别人。那么替换掉谁呢?就是替换掉那个最该被它替换的那个。也就是在d数组中第一个大于它的。第一个意味着前面的都小于等于它。假设第一个大于它的是d[j],说明d[1..j-1]都小于等于它,那么它完全可以接上d[j-1]然后生成一个长度为j的不下降子序列,而且这个子序列比当前的d[j]这个子序列更有潜力(因为这个数比d[j]小)。所以就替换掉它就行了,也就是d[j]=a[i]。其实这个位置也是它唯一能够替换的位置(前面的替了不满足d[k]最小值的定义,后面替换了不满足不下降序列)
至于第一个大于它的怎么找……STL upper_bound。每次复杂度logn。
2、不上升子序列个数,根据Dilworth定理得知:
(1)U的链划分使用的最少集合数,等于它的最大反链长度。
(2)U的反链划分使用的最少集合数,等于它的最大链长度。
(大概可以理解为,一个集合,其中的序列最小划分数等于反向的最长序列长度)
根据这个定理,就可以得知,不上升子序列的数量就是上升子序列的最大长度(而不下降的相反就是下降)
所以答案就是上升子序列的最大长度,解决方法如(1)
AC代码:
#include<bits/stdc++.h>
#define MAX_N 100005
using namespace std;
//struct cmp{bool operator()(int a,int b){return a>b;}};
int main(){
int n,lendown,lenup;
int sum=0;
int down[MAX_N];
int up[MAX_N];
int a[MAX_N];
while(scanf("%d",&n)!=EOF){
a[++sum]=n;
}
if(sum==0){
cout<<"0"<<endl;
cout<<"0"<<endl;
return 0;
}
down[1]=a[1];
up[1]=a[1];
lendown=1;
lenup=1;
// for(int i=1;i<=sum;i++)cout<<a[i]<<" ";
for(int i=2;i<=sum;i++){
if(a[i]<=down[lendown])
down[++lendown]=a[i];
else{
int j=upper_bound(down+1,down+lendown+1,a[i],greater<int>())-down;
// cout<<j<<endl;
down[j]=a[i];
}
if(a[i]>up[lenup])
up[++lenup]=a[i];
else{
int j=lower_bound(up+1,up+lenup+1,a[i])-up;
up[j]=a[i];
}
}
cout<<lendown<<endl<<lenup<<endl;
// for(int i=1;i<=lendown;i++){
// cout<<down[i]<<" ";
// }cout<<endl;
// for(int i=1;i<=lenup;i++){
// cout<<up[i]<<" ";
// }cout<<endl;
return 0;
}