最長不下降子序列nlogn算法詳解
今天花了很長時間終於弄懂了這個算法……畢竟找一個好的講解真的太難了,所以勵誌我要自己寫一個好的講解QAQ
這篇文章是在懂了這個問題n^2解決方案的基礎上學習。
解決的問題:給定一個序列,求最長不下降子序列的長度(nlogn的算法沒法求出具體的序列是什麽)
定義:a[1..n]為原始序列,d[k]表示長度為k的不下降子序列末尾元素的最小值,len表示當前已知的最長子序列的長度。
初始化:d[1]=a[1]; len=1; (0個元素的時候特判一下)
現在我們已知最長的不下降子序列長度為1,末尾元素的最小值為a[1],那麽我們讓i從2到n循環,依次求出前i個元素的最長不下降子序列的長度,循環的時候我們只需要維護好d這個數組還有len就可以了。
關鍵問題就是怎麽維護?
可以看出我們是要用logn的復雜度維護的。實際上利用了d數組的一個性質:單調性。(長度更長了,d[k]的值是不會減小的)
考慮新進來一個元素a[i]:
如果這個元素大於等於d[len],直接讓d[len+1]=a[i],然後len++。這個很好理解,當前最長的長度變成了len+1,而且d數組也添加了一個元素。
如果這個元素小於d[len]呢?說明它不能接在最後一個後面了。那我們就看一下它該接在誰後面。
準確的說,並不是接在誰後面。而是替換掉誰。因為它接在前面的誰後面都是沒有意義的,再接也超不過最長的len,所以是替換掉別人。那麽替換掉誰呢?就是替換掉那個最該被它替換的那個。也就是在d數組中第一個
至於第一個大於它的怎麽找……STL upper_bound。每次復雜度logn。
至此,我們就神奇的解決了這個問題。按照這個思路,如果需要求嚴格遞增的子序列怎麽辦?
仍然考慮新進來一個元素a[i]:
如果這個元素大於d[len],直接讓d[len+1]=a[i],然後len++。這個很好理解,當前最長的長度變成了len+1,而且d數組也添加了一個元素。
如果這個元素小於等於d[len]呢?說明它不能接在最後一個後面了。那我們就看一下它該接在誰後面。
同樣的道理,只是upper_bound的時候要特判一下。每次復雜度logn。
下面是最長不下降子序列的代碼
//最長不下降子序列nlogn Song
#include<cstdio>
#include<algorithm>
using namespace std;
int a[40005];
int d[40005];
int main()
{
int n;
scanf("%d",&n);
for (int i=1;i<=n;i++) scanf("%d",&a[i]);
if (n==0) //0個元素特判一下
{
printf("0\n");
return 0;
}
d[1]=a[1]; //初始化
int len=1;
for (int i=2;i<=n;i++)
{
if (a[i]>=d[len]) d[++len]=a[i]; //如果可以接在len後面就接上
else //否則就找一個最該替換的替換掉
{
int j=upper_bound(d+1,d+len+1,a[i])-d; //找到第一個大於它的d的下標
d[j]=a[i];
}
}
printf("%d\n",len);
return 0;
}
想了一晚上這個問題終於想通了。前面說的“最該替換的位置”實際上不是很精確,那個位置替換掉是它唯一能夠替換的位置,之所以要替換,就是為了維護d這個數組,讓它始終滿足最初的定義。
最長不下降子序列nlogn算法詳解