树状数组与最长下降子序列

还记得大约两年前我写过一篇博客《最长严格上升子序列》,里面使用了一种$ O(n^2) $的算法来求解其最大长度,两年之后,我们继续讨论这个问题。但是这一次,我们介绍一种$ O(nlogn) $的算法。

直接上代码,用注释的方式讲解

#include <iostream>
#define MAXN 100000
using namespace std;

int a[MAXN];
int dp[MAXN];
int count,maxnum;
int ans;

int lowbit(int x) {
    return x&(-x);
}
//就是一个树状数组lowbit函数
void add(int x,int data) {
    for(int i=x;i<=maxnum;i+=lowbit(i))
        dp[i] = max(dp[i],data);
}
//在这个函数里,我们维护一个树状dp数组,向后更新
//因为我们是向后增加这一个链的长度,由低点影响高点
//如果不这么做,可能会使结果变大(data往往比dp数组中低位的数据大)
int query(int x) {
    int r = 0;
    for(int i=x;i>=1;i-=lowbit(i))
        r = max(dp[i],r);

    return r;
}
//返回小于等于x的数的个数的最大值
int main() {
    ios::sync_with_stdio(false);//读入优化

    int t;
    while(cin>>t) {
        a[++count] = t;//输入数据
        maxnum = max(maxnum,t);//求区间最大范围
    }

    --count;//count最好减1防止后面忘记

    for(int i=count;i>=1;--i) {
        //因为我们的add函数是向后更新数据的,所以这里倒序加入,其实也就是把问题反了过来
        //维护的其实是一个最长上升子序列的树状数组
        int q = query(a[i])+1;//查询小于等于a[i]的数的个数,尝试把当前值加入
        add(a[i],q);
        ans = max(ans,q);//当前尝试的值与答案比较取优解
    }

    cout<<ans<<endl;

    return 0;
}

同时我们还可以根据Dilworth定理求元素不重复使用的不上升子序列(也就是子链元素和等于母链元素和)的个数。

也就是反过来求这个序列的不下降子序列的最大长度,同样用树状数组维护,只是这一次我们正序输入数据,算法复杂度还是$ O(n\log{n}) $。

同样,求最长不下降子序列也很好实现——只需要询问a[i]-1就行。

Last modification:December 3rd, 2018 at 07:14 pm
博客维护不易,如果你觉得我的文章有用,请随意赞赏

Leave a Comment