数列分块(长期更新)

分块是优雅的暴力——一位\(dalao\)

hzwer dalao的blog

level 1

题面

给出一个长为\(n\)的数列,以及\(n\)个操作,操作涉及区间加法,单点查值。

数据范围与约定

对于\(100\)%的数据,\(1\le n\le 50000,others/ans\in INT\)

题解

作为分块的第一题,它具有最小的难度和最强的代表性.题目要求区间修改单点查询,事实上通过树状数组也是可以完成的.那么接下来我们看看分块怎么做.

分块就像字面意思,把数列分成若干块,使得信息的统计和修改更加容易不必精确到点,节省了时间.分块主要分两种情况讨论:

  1. 不完整的块只需要靠暴力来扫即可.
  2. 完整的块可以做整体操作来节省时间,这也是分块和暴力相比最优越的地方.

分多少块呢?如果我们每块的大小是\(size\),则处理每一整块需要\(\Theta(1)\)的复杂度,一共有\(n/m\)块,两边不完整的块最多有\(2m\)个元素,所以复杂度是\(\Theta(n/m)+\Theta(m)\),根据均值不等式,我们可以得出当分\(\sqrt{n}\)块时复杂度是最小的.可以通过代码来感受一下分块过程:

struct node {
    int tag, l, r;
}part[N];//块结构体
...
int size = sqrt(n)//每块的大小;
part[1].l = 1;
for (int i = 1; i <= n; ++i) {
    bel[i] = cnt;//序列中第i个位置属于第几块
    if (i % size == 0) {//一整块
        part[cnt].r = i;
        part[++cnt].l = i + 1;//增加新的块
    }
}
part[cnt].r = n;

接下来我们回到问题,来分析一下该怎么实现区间修改和单点查询.因为要按块来整体操作,所以我们对每一个块打一个\(tag\),表示加法标记.当我们的区间修改覆盖整块的时候,我们直接令这一块的\(tag\)增加.如果不是一整块就暴力扫描.当我们要单点查询的时候,只需要让数列中的值加上它所属的块的加法标记即可.

read(opt);
if (opt) {
    int x, y, z;
    read(x), read(y), read(z);
    printf("%lld\n", a[y] + part[bel[y]].tag);//输出只需要加上加法标记.
}
else {修改需要判定一些边界,如是不是完整块,左右端点是不是在同一块中.
int x, y, z;
read(x), read(y), read(z);
if (part[bel[x]].l == x && part[bel[y]].r <= y)
    part[bel[x]].tag += z;//如果左端点恰好是一块的起点,区间覆盖一整块就直接加
else
    for (int i = x; i <= min(part[bel[x]].r, y); ++i)
        a[i] += z;//x,y可能同块,所以要取min
if (bel[x] == bel[y])
    continue;
if (part[bel[y]].r == y && part[bel[y]].l <= x)
    part[bel[y]].tag += z;
else
    for (int i = y; i >= part[bel[y]].l; --i)
        a[i] += z;
for (int i = bel[x] + 1; i <= bel[y] - 1; ++i)
    part[i].tag += z;//把剩下的块直接处理
}

这样我们就完成了第一个分块题.

猜你喜欢

转载自www.cnblogs.com/i-cookie/p/11628422.html
今日推荐