跳转至

分块

前置知识

树状数组,线段树

目标

分块思想,模板题

What

树状数组基于二进制划分与倍增思想,线段树基于分治思想。 二者把序列中的元素聚合成大大小小的“段”,花费额外的代价,对“段”进行维护,从而使得每个区间的信息可以快速由几个已有的“段”结合而成。 分块的基本思想是,通过适当的划分,预处理一部分信息并保存下来,用空间换取时间,达到时空平衡。

img

例题,POJ3468 A Simple Problem with Integers

题意,对一个数组进行两种操作,“C a b c”,对[a, b]区间加上c,“Q a b" 查询[a, b]区间的和

#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>

using namespace std;

typedef long long ll;

const int N = 1e5 + 10;

ll arr[N], sum[N], add[N];
int L[N], R[N]; // each segment [l, r]
int bel[N]; // maintain the point belong which segment
int n, T, B;

void change(int l, int r, int d) {
    int p = bel[l], q = bel[r];

    // belong to a same block
    if (p == q) {
        for (int i = l; i <= r; i++) arr[i] += d;
        sum[p] += 1ll * (r - l + 1) * d;
    }
    else {
        for (int i = p + 1; i <= q - 1; i++) add[i] += d;

        for (int i = l; i <= R[p]; i++) arr[i] += d;
        sum[p] += 1ll * (R[p] - l + 1) * d;

        for (int i = L[q]; i <= r; i++) arr[i] += d;
        sum[q] += 1ll * (r - L[q] + 1) * d;
    }

    return ;
}

ll ask(int l, int r) {
    int p = bel[l], q = bel[r];
    ll ret = 0;

    if (p == q) {
        for (int i = l; i <= r; i++) ret += arr[i];
        ret += add[p] * (r - l + 1);
    }
    else {
        for (int i = p + 1; i <= q - 1; i++) ret += sum[i] + add[i] * (R[i] - L[i] + 1);

        for (int i = l; i <= R[p]; i++) ret += arr[i];
        ret += add[p] * (R[p] - l + 1);

        for (int i = L[q]; i <= r; i++) ret += arr[i];
        ret += add[q] * (r - L[q] + 1);
    }

    return ret;
}

int main() {
    cin >> n >> T;
    for (int i = 1; i <= n; i++) scanf("%lld", &arr[i]);

    // sqrt
    B = sqrt(1.0 * n);
    for (int i = 1; i <= B; i++) {
        L[i] = (i - 1) * B + 1;
        R[i] = i * B;
    }
    if (R[B] < n) {
        B++;
        L[B] = R[B - 1] + 1;
        R[B] = n;
    }

    // init
    for (int i = 1; i <= B; i++) 
        for (int j = L[i]; j <= R[i]; j++) {
            bel[j] = i;
            sum[i] += arr[j];
        }

    // ask and answer
    while (T--) {
        char op[3];
        int l, r, d;
        scanf("%s%d%d", op, &l, &r);

        if (op[0] == 'C') {
            scanf("%d", &d);
            change(l, r, d);
        }
        else printf("%lld\n", ask(l ,r));
    }

    return 0;
}

段数 和 段长,都是 � ,所以,整个算法的时间复杂度为 �((�+�)∗�) 。

题单

POJ3468 A Simple Problem with Integers LOJ#6280. 数列分块入门 4 UVA-12003Array Transformer Codeforces - Destiny Codeforces - Holes Codeforces - XOR and Favorite Number Codeforces - Powerful array

总结

常见的分块思想,可以用“大段维护、局部朴素”来形容。

参考

《进阶指南》 kkksc03:【洛谷日报#20】浅谈基础根号算法——分块