当解决最长不下降子序列时,常见的做法是从最后一位向前枚举,同时从该位向后枚举,如果当前位a[ i ]比a[ j ]大且f[ i ]<f[ j ]+1,那么f[ i ]=f[ j ]+1,最后再从前向后扫一遍查询最大值。显然,这种做法并不优秀【什么鬼】,如果序列元素的个数大一点就很容易超时,这里介绍一种N(log N)级别的算法,可以解决当元素个数达到1e5的问题。
思路:我们用dp[ i ]表示处理到当前位置时,之前的整数能构成的长度为i的序列中,第i位上的最小值【即系列中最后一位最小值】,对于dp[ i ],用二分查找的方法确定。按顺序从左到右,计算每个数字作为序列的最后一位,就能构成最长的序列。
-------------------------------------------------------PS:灵魂画师已上线--------------------------------------------------
接下来图示一波:
现在给出14个数:13 7 9 16 38 24 37 18 4 19 21 22 63 15
变量声明:tail表示已记录的最长序列长度,最后直接输出即可。
low为下届,high为上界。
DP过程如下【只介绍前两个的过程】:
初始情况:
第一次二分:
第二次二分:
剩下的以此类推,因为全部扫一遍,复杂度为O(N),对每个元素都要二分,复杂度O(log N),所以总的复杂度为O(N log N).。
下面贴代码:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e5+10;
int n,a,mid,dp[MAXN];
int main()
{
dp[0]=0;
scanf("%d",&n);
int tail=0;
for(int i=1;i<=n;++i)
{
scanf("%d",&a);
int low=0,high=tail-1;
while(low<=high)
{
mid=(low+high)>>1;
if(dp[mid]>=a)
high=mid-1;
else
low=mid+1;
}
if(low>=tail)
tail++;
dp[low]=a;
}
printf("%d",tail);
return 0;
}