最长上升子序列的$O(nlogn)$做法。

之前用到的较朴素的做法复杂度是$O(n^2)$的。

算法瓶颈在于每次转移时都要枚举获取最大值,我们考虑离散化整个序列,计算$f_i$时相当于从$1 $到$ a_i - 1$中选择一个$f$值最大的转移到$f_i$。

前缀最大值 —— 这正是树状数组的拿手好戏!

于是转移复杂度降到$O(logn)$,整个算法复杂度降到$O(nlogn)$。

【代码】

COGS 1398 最长上升子序列

#include <cstdio>
#include <algorithm>
#include <climits>
#include <cstring>

#define MAXN 5000

int a[MAXN + 1];

int max[MAXN + 1];
int n;

int lowbit(signed int x){
    return x & -x;
}

int query(int i){
    int ans = INT_MIN;
    while(i){
        ans = std::max(ans, max[i]);
        i -= lowbit(i);
    }
    return ans;
}

void set(int i, int x){
    while(i <= n){
        max[i] = std::max(max[i], x);
        i += lowbit(i);
    }
} // 严格来说不能算修改,只能算设初值

int main(){
    // freopen("lis1.in", "r", stdin), freopen("lis1.out", "w", stdout);

    scanf("%d", &n);
    for(int i = 0; i < n; i++) scanf("%d", a + i);

    static int aux[MAXN + 1];
    memcpy(aux, a, sizeof a);
    std::sort(aux, aux + n);
    int size = std::unique(aux, aux + n) - aux;
    for(int i = 0; i < n; i++){
        a[i] = std::lower_bound(aux, aux + size, a[i]) - aux + 1;
    }

    int ans = INT_MIN;
    for(int i = 0; i < n; i++){
        int x;
        if(a[i] == 1) x = 1; // 注意边界
        else x = query(a[i] - 1) + 1;
        ans = std::max(ans, x);
        set(a[i], x);
    }

    printf("%d\n", ans);

    // fclose(stdin), fclose(stdout);
    return 0;
}

就是这样啦