【模板】最长不下降子序列
====接力dalao完成====
前文链接:(CSP-S RP++!)
对前文的一些补充:
首先清楚最长不下降子序列是一个递增但是允许不同位元素相等的序列。而最长上升子序列则是一个单调递增的序列。
而两者都是子序列,所以子序列的长度一定小于等于原序列。且子序列在原序列的位置不一定连续。
这个O(nlogn)的算法使用的是贪心的思想。
为了帮助理解,请与以下代码对比阅读:
#include<iostream> using namespace std; int a[1000001],dp[1000001]; int ans; int n; int main() { cin>>n; for(int i=1;i<=n;i++) { cin>>a[i]; } for(int i=2;i<=n;i++) { for(int j=1;j<=i;j++) { if(a[j]<a[i]) dp[i]=max(dp[j]+1,dp[i]);//暴力从原序列中找比当前位置小的数,并不断更新当前位置的最长序列值(比较好理解) //想不懂就。。。再想想吧// } ans=max(dp[i],ans); } cout<<ans<<endl; return 0; }
这个是O(n2)的暴力算法,大概思路就是每次选取一个终点,再暴力求出终点的最长序列值,再从答案中选取最大值。
而O(nlogn)的实现过程与这个正好相反。
因为每次暴力更新都会有很多不必要的比对,比如对于原序列
1 2 3 4 5
当前选定的终点是5,在上面的暴力程序中,对于位置5会与1,2,3,4各比较一次,然后得出最后答案。然而,因为这个队列已经是单调递增的,所以5只需要与前一位4比对一次就可以得出答案,从而省去前面3次无用的比对。
为了避免浪费时间,这里再开一个数组d来存储已经找出的性价比最高的最长不下降子序列。
这里的“性价比”是指如果采用当前这个子序列作为既定的开头,用于下面继续比对,这样得出的答案一定是最优的。
举例来说:
对于原序列:
1 2 5 7 8 1 10
有下列子序列
a:1 5 7 8
b:1 2 5 8
称b的性价比高于a,其原理是,对于b数组中相邻两个元素的差要小于a数组中的,而且最后一个元素要比a数组的小,此时称b的性价比比a高。
而对于性价比更高的序列,再接着处理时,最终所得的结果是最优的。(有最优子结构)
而对于任意一个位置,若在原序列中的a[i]>d[len],其中len为d的长度,那么d[len++]=a[i];
这个的原理很简单,但是当a[i]<d[len]时,应该怎么处理呢?
去寻找d数组中第一个第一个大于a[i]的数,让a[i]与该数互换位置,得到性价比更高的序列,这次操作的原因已经在上文中阐述过。
又因为在上述操作过程中,d数组是一个不下降序列,所以可以用STL中的upper_bound来简化过程。
(upper_bound在dalao的题解里已经有解释,再复制一下)
首先介绍两个STL,非常好用(用于解决这道题)
(球球你看看它,如果看不懂就先看算法再看它,超级省事的)
lower_bound与upper_bound
- 使用二分的思想
- (所以要求在一个有序的序列内(你乐意的话自己定义一种排序方式也行,但是要有序(不然你也不知道它会出来什么乱七八糟的)))
- (默认为升序)
- 复杂度大致为 log n
用法:lower_bound(a+1,a+n+1,x)
返回 a 数组内 第一个大于等于 x 的数的指针
令 P = lower_bound(a+1,a+n+1,x)- a,a [ p ] 则 为第一个大于等于 x 的数
(如果你会指针的话) * p = lower_bound(a+1,a+n+1,x)也是 第一个大于等于 x 的数
upper_bound 和它的用法差不多,除了返回的是第一个大于 x 的指针
(也就是求最大不下降子序列和最大上升子序列的差别)
若我们要求下降序列呢 ?
我们可以写一个 cmp,或者使用 C++ 自带的 greater<>() (都在STL里)
(和 sort 写法差不多)(sort总该写过的)
lower_bound(a,a+1,x,cmp) / lower_bound(a,a+1,x,greater<>());
一个小小的问题:
是怎样保证d数组中的数在原数组中的下标也是递增的呢?
这个问题是不用考虑的,因为最后决定答案的是len,并不是d数组或是数组内的元素决定的,d数组内只是存储可行的最优解,用来优化答案。
若当前原数组中存在一条比当前d数组更长的最长不下降子序列,才会影响最后的答案长度。
这样问题就不大了(看不懂可以模拟几组数组来理解一下)
代码:
#include<iostream> #include<cstdio> #include<algorithm> using namespace std; int a[100001],d[100001],n,len; int main() { cin>>n; for(int i=1;i<=n;i++) { cin>>a[i]; } d[1]=a[1]; len=1; for(int i=2;i<=n;i++) { if(a[i]>d[len]) d[++len]=a[i]; else { int j=upper_bound(d+1,d+len+1,a[i])-d; d[j]=a[i]; } for(int i=1;i<=n;i++) { cout<<d[i]<<" "; } cout<<endl;//这个是分段输出,帮助理解 } cout<<len<<endl; return 0; }
无注释的点这里:
#include<iostream> #include<cstdio> #include<algorithm> using namespace std; int a[100001],d[100001],n,len; int main() { cin>>n; for(int i=1;i<=n;i++) { cin>>a[i]; } d[1]=a[1]; len=1; for(int i=2;i<=n;i++) { if(a[i]>d[len]) d[++len]=a[i]; else { int j=upper_bound(d+1,d+len+1,a[i])-d; d[j]=a[i]; } } cout<<len<<endl; return 0; }
应该就这样了(看不懂我也。。自己搜吧)
dalao友情提供的例题:(这个hin经典) (这个超级妙)
掰掰
我写完了@莳萝萝
CSP-S RP++!