洛谷:P2880:Balanced Lineup


洛谷:P2880:Balanced Lineup

题目

P2880:Balanced Lineup

分析

这道题目的切入点是两个:ST表和分治。

我们先讨论比较简单的情况。

假定一共有8头牛,我们要求区间也包括这8头牛(也就是\([a, b]\)的长度是8)。我们当然不能用遍历的方式去找最高、最低值——因为这种做法不具有推广性。

分治的出发点

我们采用的做法是分而治之:

flowchart TD A["区间[1, 8]的最大值"]--> B["区间[1, 4]的最大值"] A--> C["区间[5, 8]的最大值"] B--> D["区间[1, 2]的最大值"] B--> E["区间[3, 4]的最大值"] D--> F["区间[1, 1]的最大值"] D--> G["区间[2, 2]的最大值"]

这样做的好处是,我们可以用递归/递推来实现。而且,对于长度为1的区间,是不用计算的——因为此时的最大值就是元素本身。

问题来了:如果区间不是\(2^k\)怎么办?没关系,我们可以凑一下。比如对于查询区间为6,我们可以凑成4+4。虽然这么做,会造成重复,但统一了处理逻辑,是可行的。

ST表的建立

将上文“分治”的思路反过来执行,就可以构造出ST表。

void build_st()
{
    // 初始化长度为1的区间
    for (int i = 1; i <= n; i++)
    {
        st_max[i][0] = h[i];
        st_min[i][0] = h[i];
    }

    // 递推计算其他区间
    for (int j = 1; (1 << j) <= n; j++)
    {
        for (int i = 1; i + (1 << j) - 1 <= n; i++)
        {
            // 区间[i, i+(2^j)-1]的最大值等于左半部分和右半部分的最大值的较大者
            st_max[i][j] =
                max(st_max[i][j - 1], st_max[i + (1 << (j - 1))][j - 1]);
            // 区间[i, i+(2^j)-1]的最小值等于左半部分和右半部分的最小值的较小者
            st_min[i][j] =
                min(st_min[i][j - 1], st_min[i + (1 << (j - 1))][j - 1]);
        }
    }
}

首先,代码初始化长度为1的区间的最值,并用st_max[i][j]表示从第i个元素开始,长度为\(2^j\)的区间的最大值。这个处理很直截了当。1

然后,开始增加区间的长度,而且每次都是加倍(以体现\(2^k\)的特性)。此时,确定该区间st_max[i][j]的最大值就可以用递推的方式:

也就是代码中的:st_max[i][j] = max(st_max[i][j - 1], st_max[i + (1 << (j - 1))][j - 1]);

处理查询

对于某个查询中给出的一个区间\([l, r]\),我们应该将其分成符合ST表分割的两个部分(可以重叠)。方法是:

int k = log2(r - l + 1);
return max(st_max[l][k], st_max[r - (1 << k) + 1][k]);

我们先求出这个区间的长度(\(r-l+1\)),然后得到使得\(2^k\le (r-l+1)\)最大的那个\(k\)。然后将原来的区间分为两部分:\(st\_max[l][k], st\_max[r - (1 << k) + 1][k]\)

注意,这两部分区间可以重叠,但对最后结果没有影响。

综合上述算法,就可以得到最终的代码。

答案

Solution

思考

有关“ST表”以及一些相关概念的讨论,这里有一篇很好的文章可以参考。

另外,可以思考一下:为什么st_max[l][k], st_max[r - (1 << k) + 1][k]这两个区间一定会全覆盖原来的区间。


  1. 最小值的处理类似,所以下文就不再单独讨论。 

Previous Next