分桶法是把一排数据或者是一个平面分成很多个桶,每个桶维护自己内部的信息。平方分割是把n个元素,按照每√n个分为一个桶的做法。

  1. 基于平方分割的RMQ
    给定一个数列a1,a2,…,an,目标是在O(根号n)复杂度内实现两个功能

给定s,t,求as,as+1,…,at的最小值

给定t, x,把ai的值变为x.

基于平方分割RMQ的预处理
令b=floor(√n),把a中的元素每b分成一个桶,并且计算出每个桶内的最小值。

2、基于平方分割的RMQ的查询

  • 如果桶完全包含在区间内,则查询桶的最小值
  • 如果元素所在的桶不完全被区间包含,则逐个检查最小值

基于平方分割的RMQ的值的更新
在更新元素的值时,需要更新该元素所在的桶的最小值。这时只要遍历一遍桶内的元素就可以了。

3、基于平方分割的RMQ的时间复杂度
在更新值时,因为每个桶内有b个元素,所以时间复杂度是O(b)。
而在查询时

  • 完全包含在区间内的桶的个数是O(n/b)
  • 所在的桶不被区间完全包含的元素个数是O(b)
    因为设b=√n,则操作的时间复杂度是
    O(n/b+b)=O(√n+√n)=O(√n);
  1. 平方分割和线段树
    因此,在平方分割中,对于任意区间,完全包含于其中的桶的数量和剩余元素的数量都是O(√n),所以可以在O(√n)时间内完成各种操作。
    在上面的RMQ的例题中,线段树进行各种操作的复杂度是O(logn),比平方分割更快一些。一般地,如果线段树和平方分割都能实现某个功能,多数情况下线段树会比平方分割快。但是,因为平方分割在实现上比线段树简单,所以如果运行时间限制不是太紧时,也可以考虑使用平方分割。除此之外,也有一些功能是线段树无法高效维护但是平方分割却可以做到的。

题目:POJ2104

(下面的代码应该是poj太垃圾,所以TLE了…)

半夜十一点五十分,估摸着poj没人在用了,交一发,选择G++编译器,然后,AC了…

#pragma GCC optimize("O3")
#include <cstdio>
#include <vector>
#include <algorithm>
#include <string.h>

using namespace std;
typedef long long ll;
#define MAXN 100005

int n, m;
int a[MAXN], num[MAXN];

const int b_size = 1000;
vector<int> bucket[MAXN / b_size + 2];


inline int read(){
    register int x = 0, t = 1;
    register char ch=getchar(); // 读入单个字符到寄存器
    while(ch<'0'||ch>'9'){
        if(ch=='-')
            t=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        x=(x<<1)+(x<<3)+(ch^48);  // 移位与异或
      	// 第十行可以换成 x = x * 10 + ch - '0'
        ch=getchar();
    }
    return x*t;
}

inline void solve()
{
    for (int i = 0; i < n; ++i)
    {
        bucket[i / b_size].push_back(a[i]);
        num[i] = a[i];
    }

    //memcpy(num, a, sizeof(a));

    sort(num, num + n);

    int bnum = n / b_size + 1;
    for (int i = 0; i <= bnum; ++i)
        sort(bucket[i].begin(), bucket[i].end());

    int l, r, k;
    while (m--)
    {
        l=read();
        r = read();
        k = read();
        //scanf("%d%d%d", &l, &r, &k);
        int lb = -1, rb = n;
        --l;
        while (lb + 1 < rb)
        {
            int mid = (lb + rb) >> 1;
            int x = num[mid];

            //c为区间中有多少数小于x
            int tl = l, tr = r, c = 0;

            //计算x在两边不在一个桶中的次序
            while (tl < tr && tl % b_size != 0)
                if (a[tl++] <= x)
                    ++c;
            while (tl < tr && tr % b_size != 0)
                if (a[--tr] <= x)
                    ++c;

            while (tl < tr)
            {
                int bb = tl / b_size;
                c += upper_bound(bucket[bb].begin(), bucket[bb].end(), x) - bucket[bb].begin();
                tl += b_size;
            }

            if (c >= k)
            {
                //x过大
                rb = mid;
            }
            else
                lb = mid;
        }
        printf("%d\n", num[rb]);
    }
}
int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 0; i < n; ++i)
        //scanf("%d", &a[i]);
        a[i]=read();
    solve();

    return 0;
}

转载请注明来源:https://www.longjin666.top/?p=1213

欢迎关注我的公众号“灯珑”,让我们一起了解更多的事物~

你也可能喜欢

发表评论