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

如果直接使用dp的话,时间复杂度是O(n^2)的,因为每次在i点时,都要遍历i点之前的所有点,来找出最优解。那么一种优化方法较为直观,容易理解,使用树状数组或者线段树维护前缀的最大值,这样每查询的时候只需要O(logn)的复杂度即可。
这里附上树状数组的解法,我是在这里才学到用树状数组维护前缀极值的。。。

#include
#include
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
#include
#include
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;
}

你可能感兴趣的:(最长上升子序列(ologn算法))