最長上升子序列(dp--O(n*logn))

O(n^2)時遞推關係簡單, 代碼實現也簡潔, 唯一的問題是n ^ 2的複雜度在題目給的數據量較大時會超時。

這個問題可以用二分來優化。

做法是構造出一個新的有序的DP數列, 用原數列中的數從左到右維護更新新數列。

初始時DP[0] = s[0], 從i = 1時遍歷原數列, 將每個遍歷的數與DP數列的末尾進行比較, 如果大於末尾, 則把DP數列長度提1將s[i]放在DP數列的最後, 如果小於末尾那麼替換掉DP數列中比s[i]大的第一個數。

結束後DP數列的長度就是LIS的長度。

從LIS的性質出發,要想得到一個更長的上升序列,該序列前面的數必須儘量的小。

對於序列1,5,8,3,6,7來說,當子序列爲1,5,8時,遇到3時,序列已經不能繼續變長了。但是,我們可以通過替換,使“整個序列”看上去更小,從而有更大的機會去變長。這樣,當替換5-3和替換8-6完成後(此時序列爲1,3,6),我們可以在序列末尾添加一個7了。

那爲什麼複雜度可以是O(NlogN)呢?

關鍵就在“替換”這一步上,若直接遍歷序列替換,每次替換都要O(N)的時間。但是隻要我們再次利用LIS的性質——序列是有序的(單調的),就可以用二分查找,在O(logN)的時間內完成一次替換,所以算法的複雜度是O(NlogN)的。

 

代碼:

#include <iostream>
#include <algorithm>
using namespace std;

int main()
{
    int n;
    int a[1100];
    int dp[1100];//記錄最大子序列
    int pos[1100];//記錄最大子串的下標
    cin>>n;
    for(int i=0;i<n;i++){
        cin>>a[i];
    }
    int j=0;
    dp[0]=a[0];
    pos[0]=0;
    for(int i=1;i<n;i++){
        if(a[i]>dp[j]){
            j++;
            dp[j]=a[i];
            pos[j]=i;
        }
        else{
            //flag表示dp中>=a[i]的第一個數的下標
            int flag=lower_bound(dp,dp+j,a[i])-dp;
//            cout<<flag<<endl;
//            cout<<*lower_bound(dp,dp+j,a[i])<<endl;
            dp[flag]=a[i];
            pos[flag]=flag;
        }
    }

    cout<<j+1<<endl;
//    最長子序列的和
//    int res=0;
//    for(int i=0;i<=j;i++){
//        res+=dp[pos[i]];
//    }
//    cout<<res<<endl;
    return 0;
}


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章