最長上升子序列(ologn算法)

如果直接使用dp的話,時間複雜度是O(n^2)的,因爲每次在i點時,都要遍歷i點之前的所有點,來找出最優解。那麼一種優化方法較爲直觀,容易理解,使用樹狀數組或者線段樹維護前綴的最大值,這樣每查詢的時候只需要O(logn)的複雜度即可。
這裏附上樹狀數組的解法,我是在這裏才學到用樹狀數組維護前綴極值的。。。

#include<iostream>
#include<algorithm>
using namespace std;
#define lowbit(i) ((-i) & (i))
const int MAXN = 1e5+5;

int a[MAXN];
int dp[MAXN];

int n;
int c[MAXN];
void update(int x, int v){
    for(int i = x; i <= n; i = i+lowbit(i)){
        c[i] = max(c[i], v);
    }
}
int query(int x){
    int maxx = 0;
    for(int i = x; i >= 1; i = i-lowbit(i)){
        maxx = max(c[i], maxx);
    }
    return maxx;
}
int main(){
    cin >> n;
    for(int i = 1; i <= n; ++i)
        cin >> a[i];
    dp[1] = 1;
    update(a[1], dp[1]);
    int res = 0;
    for(int i = 2; i <= n; ++i){
        int maxx = query(a[i]);
        dp[i] = maxx+1;
        update(a[i], dp[i]);
        if(res < dp[i]) res = dp[i];
    }
    cout << res << endl;
    return 0;
}

還有一種O(nlogn)的算法,就是二分。
新建一個stk數組,stk[i]表示長度爲i的LIS結尾元素的最小值。對於一個上升子序列,顯然其結尾元素越小,越有利於在後面接其他的元素,也就越可能變得更長。因此,我們只需要維護stk數組,對於每一個a[i],如果a[i] > stk[當前最長的LIS長度],就把a[i]接到當前最長的LIS後面,即stk[++當前最長的LIS長度]=a[i]。
那麼,怎麼維護stk數組呢?
對於每一個a[i],如果a[i]能接到LIS後面,就接上去;否則,就用a[i]取更新stk數組。具體方法是,在stk數組中找到第一個大於等於a[i]的元素stk[j],用a[i]去更新stk[j]。如果從頭到尾掃一遍stk數組的話,時間複雜度仍是O(n^2)。我們注意到stk數組內部一定是單調不降的,所有我們可以二分stk數組,找出第一個大於等於a[i]的元素。二分一次stk數組的時間複雜度的O(lgn),所以總的時間複雜度是O(nlogn)。

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN = 1e5+5;

int n;
int a[MAXN];
int stk[MAXN], top;

int main(){
    cin >> n;
    for(int i = 1; i <= n; ++i){
        cin >> a[i];
    }
    stk[++top] = a[1];
    for(int i = 2; i <= n; ++i){
        if(a[i] >= stk[top]){
            stk[++top] = a[i];
        }
        else{
            int cur = lower_bound(stk+1, stk+1+top, a[i]) - stk;
            stk[cur] = a[i];
        }
    }
    cout << top << endl;
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章