1. 程式人生 > >最長不下降子序列nlogn算法詳解

最長不下降子序列nlogn算法詳解

利用 pos 要求 它的 子序列 解決 post 第一個 就是

今天花了很長時間終於弄懂了這個算法……畢竟找一個好的講解真的太難了,所以勵誌我要自己寫一個好的講解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數組中第一個

大於它的。第一個意味著前面的都小於等於它。假設第一個大於它的是d[j],說明d[1..j-1]都小於等於它,那麽它完全可以接上d[j-1]然後生成一個長度為j的不下降子序列,而且這個子序列比當前的d[j]這個子序列更有潛力(因為這個數比d[j]小)。所以就替換掉它就行了,也就是d[j]=a[i]。其實這個位置也是它唯一能夠替換的位置(前面的替了不滿足d[k]最小值的定義,後面替換了不滿足不下降序列)

  至於第一個大於它的怎麽找……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算法詳解