洛谷P2880 [USACO07JAN] Balanced Lineup G

37 阅读1分钟

原题链接:www.luogu.com.cn/problem/P28…

一道非常经典的区间最值问题,对于这种问题,我们可以使用多种方法求解。

首先由于本题没有修改操作,整体为静态区间,所以我们可以考虑使用ST表来维护区间最值。

ST表运用了一种类似于分治的思想,将整个区间拆分为多个长度为 2k2^k 的区间,其中显然的,较大区间的最值可以由组成它的两个较小区间的最值求得,即将一个长度为 2k2^k 的区间拆分为两个长度为 2k12^{k-1} 的区间。这种思想的应用很广,例如在线段树、树状数组、Floyd算法优化、背包dp优化、倍增求LCA等等都有出现。

以求区间最大值为例,我们用 sti,jst_{i,j} 表示以 ii 为左端点,长度为 2j2^j 的区间的最大值,则有转移方程:

sti,j=max(sti,j1,sti+2j1,j1)st_{i,j}=max(st_{i,j-1},st_{i+2^{j-1},j-1})

由此则可以从小区间推到大区间。

那么我们怎么进行查询呢?设查询区间为 [l,r][l,r] ,则我们设 len=rl+1len=r-l+1k=log2(len)k=log_2(len) ,分别查询 stl,kst_{l,k}str2k+1,kst_{r-2^k+1,k} ,并在其中取最大值,可以发现这刚好覆盖了整个查询区间。

coding:

class sparse_table
{
private:
    vector<vector<int>> st;
    vector<int> lg;
    int type;

public:
    sparse_table(const vector<int> &arr, const int type)
    {
        this->type = type;
        int n = arr.size();
        int max_log = log2(n) + 1;
        st.resize(n + 1, vector<int>(max_log));

        lg.resize(n + 1);
        lg[1] = 0;
        for (int i = 2; i <= n; i++)
            lg[i] = lg[i / 2] + 1;

        for (int i = 1; i <= n; i++)
            st[i][0] = arr[i];

        for (int j = 1; j < max_log; j++)
        {
            for (int i = 1; i + (1 << j) <= n + 1; i++)
                st[i][j] = (type == 1 ? max(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]) : min(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]));
        }
    }

    int query(int l, int r)
    {
        int len = r - l + 1;
        int k = lg[len];

        return (type == 1 ? max(st[l][k], st[r - (1 << k) + 1][k]) : min(st[l][k], st[r - (1 << k) + 1][k]));
    }
};

有一个小技巧是预处理log值,这样可以减少运算次数,提高效率。

此外,对于区间最值问题,显然线段树是很容易处理的,而且可以满足动态修改和查询。

但如果我们要使用树状数组,应该怎么处理以求得区间最值呢?我们不妨看看,当我们查询区间 [l,r][l,r] 时,有以下两种情况:

rlowbit(r)>lr-lowbit(r)>l ,则我们可以将区间拆成两个部分,一个为 [l,rlowbit(r)][l,r-lowbit(r)] ,一个为 [rlowbit(r)+1,r][r-lowbit(r)+1,r] ,不难看出,其中的第二个部分的区间最值即为 bit[r]bit[r] ,则此时我们的问题就转化成了求 max(queryRange(l,rlowbit(r)),bit[r])max(queryRange(l,r-lowbit(r)),bit[r])

rlowbit(r)lr-lowbit(r)≤l ,则此时我们直接将区间拆分为 [l,r1][l,r-1] 和一个单独的点 rr ,问题就变成了求 max(queryRange(l,r1),arr[r])max(queryRange(l,r-1),arr[r])

然后我们使用递归求解,最终即得答案。

coding:

class fenwick_tree
{
private:
    int n;
    int type;
    vector<int> bit;
    vector<int> arr;

public:
    fenwick_tree(const int n, const int type)
    {
        this->n = n;
        this->type = type;
        bit.resize(n + 1, (type == 1 ? INT_MIN : INT_MAX));
    }

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

    void update(int idx, int val)
    {
        while (idx <= n)
        {
            if (type == 1)
                bit[idx] = max(bit[idx], val);
            else
                bit[idx] = min(bit[idx], val);

            idx += lowbit(idx);
        }
    }

    void initialize(vector<int> &arr)
    {
        this->arr = arr;
        for (int i = 1; i <= n; i++)
            update(i, arr[i]);
    }

    int query_range(int l, int r)
    {
        if (r > l)
        {
            if (type == 1)
            {
                if (r - lowbit(r) > l)
                    return max(query_range(l, r - lowbit(r)), bit[r]);
                else
                    return max(query_range(l, r - 1), arr[r]);
            }
            else
            {
                if (r - lowbit(r) > l)
                    return min(query_range(l, r - lowbit(r)), bit[r]);
                else
                    return min(query_range(l, r - 1), arr[r]);
            }
        }
        return arr[r];
    }
};

但说实话树状数组其实并不适合求解区间最值问题,在一般情况下总体复杂度是 O(nlog2n)O(nlog^2n) 的,在这个时候使用线段树会更优。