题目转化

首先将题目数据按照(W, L)排序。
如果(W1, L1) < (W2, L2),再加上题目给的W <= L条件,则(W2, L2)只能被放在(W1, L1)下面。
如果(W1, L1) = (W2, L2),两个积木完全相等,先后顺序没有限制,在不影响答案正确性的情况下,可以自己加上限制,排序之后排在后面的被放在下面。

这样,排在后面的积木一定被放在下方,则每种可能搭成的塔对应排序后的积木序列的一个子序列。这样,最高能够搭成的塔的层数等于L值的最长非降子序列的长度。

求解最长非降子序列

方法一、长度数组+二分

构建长度数组AA[k]表示长度为k的非降序列的最小结尾数值。
在考虑积木(W, L)时,只要A[k] <= L就可以在A[k]所代表的非降序列之后加上L值,构成长度增加1的新非降序列。
此处很重要的一点就是发现A数组本身就是非降的。因为如果存在一个长度为k的序列以A[k]结尾,则必定存在一个长度为k-1且以A[k]结尾的序列,于是A[k-1] <= A[k]成立。
有了这个结论,可以用二分法找到使得A[k]<=L的最大k值,并构建出长度为k+1的新序列,然后更新A数组。
时间复杂度O(NlogN)

代码

class Solution1{
    public:
        int maxBlock(vector<pair<int,int>> Blocks){
            int N = Blocks.size();
            sort(Blocks.begin(), Blocks.end());

            //A[i]表示长度为i的所有块序列中,最底下的一块的y值的最小值
            //根据定义,A数组是单调非降的, A[0]=0是空序列
            vector<int> A(1, 0);
            for(int i = 0 ; i < N ; ++i){
                //考虑积木(W,L)
                //二分法找出最大的k,使得 A[k] <= L
                int k = upper_bound(A.begin(), A.end(), Blocks[i].second) - A.begin() - 1;
                //构建出长度为k+1的新数组
                int newLen = k + 1;

                //用当前数组更新A数组
                if(A.size() < newLen + 1){
                    A.push_back(Blocks[i].second);
                }
                else{
                    A[newLen] = min(A[newLen], Blocks[i].second);
                }
            }
            //A数组包含A[0],所以最长序列长度为A.size()-1
            return A.size() - 1;
        }
};

方法二、离散化+树状数组

构建数组A[i]表示以积木i为底最高搭多少层。
从左到右扫描到积木(W[i],L[i])的时候,A[i] = max(A[k]) + 1,其中k < iL[k] <= L[i]。于是,需要一个数据结构,可以存储(L[k], A[k])并且支持快速查找L[k] < L[i]A[k]的最大值。

首先是离散化,将所有L值有序地映射到自然数Lid。
然后构建数组BB[Lid[k]]L[k]对应的最大A[k]
对于数组B我们需要两个操作。
第一是加入数据(Lid[k], A[k])。即B[Lid[k]] = max(B[Lid[k]], A[k])。这是点修改。
第二是求所有Lid[k] <= Lid[i]的值中的最大值,即求B数组中从1到Lid[i]这些值中的最大值。这是区间查询。

点修改+区间查询,可以使用树状数组或者线段树实现。
时间复杂度O(NlogN)

代码

class Solution2{
    // 离散化结构,将所有L值映射到自然数
    int N, Rn;
    vector<int> Rank;
    void initRank(vector<pair<int,int>> &Blocks){
        Rank.resize(N);
        for(int i = 0 ; i < N ; ++i)
            Rank[i] = Blocks[i].second;

        sort(Rank.begin(), Rank.end());
    }
    int getRank(int V){
        //找到V值第一次出现的下标,保证V值出现在Rank中
        return lower_bound(Rank.begin(), Rank.end(), V) - Rank.begin();
    }

    //树状数组结构,下标从1开始
    vector<int> Max;
    void initBIT(){
        Max = vector<int>(N+2, 0);
    }
    void Add(int X, int V){
        while(X <= N){
            Max[X] = max(Max[X], V);
            X += X & -X;
        }
    }
    int Query(int X){
        int maxLen = 0;
        while(X > 0){
            maxLen = max(maxLen, Max[X]);
            X -= X & -X;
        }
        return maxLen;
    }

    public:
        int maxBlock(vector<pair<int,int>> Blocks){
            N = Blocks.size();
            sort(Blocks.begin(), Blocks.end());

            initRank(Blocks);//离散化的初始化
            initBIT();//树状数组的初始化

            for(int i = 0 ; i < N ; ++i){
                //考虑积木(W, L)
                //求出L映射到的自然数之后再+1的值,因为树状数组是从1开始的
                int Lid = getRank(Blocks[i].second) + 1;
                //求以当前积木为底的最大层数
                //Query(Lid)求出了所有 Lid[k] <= Lid[i](即L[k] <= L[i]) 对应的答案中的最大值
                int cntLen = Query(Lid) + 1;
                //用以Blocks[i]结尾的最长序列更新树状数组的数据
                Add(Lid, cntLen);
            }
            //Query(N)求出以每个积木为底的最大层数中的最大值
            return Query(N);
        }
};