分块入门

Posted knife-rose

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了分块入门相关的知识,希望对你有一定的参考价值。

分块就是乱搞(确信

啥是分块

分块本质就是优雅的暴力,通过预处理和根号平衡(玄学地)让复杂度降低

比如我们考虑一个线段树裸题:

区间加,区间查询,(n<=1e5)

显然暴力的做法是(n^2)的,那么我们有没有什么优化方法呢?

我们可以将整个序列分为若干块,提前预处理出每个块的和,每次修改如果包含一个整块就直接打标记,非整块范围就暴力修改

时间复杂度

假设我们将(k)个元素分成一块,那么将一共分出(frac{n}{k})个块

对于修改和查询操作:整块(O(1))标记或查询,散块暴力修改,那么一次操作复杂度最高为(O(frac{n}{k}+k))

根据均值不等式,当(frac{n}{k}=k)时,(frac{n}{k}+k)最小,也就是说当(k=sqrt{n})时总复杂度最低为(O(nsqrt{n}))

虽然相比(n^2)很优秀,但是为啥我不写线段树呢???

相比其他数据结构优势

众所周知,线段树维护的信息需要能够区间快速合并,但是分块由于过于暴力,可以忽略这个条件

如这道例题:

区间加,区间小于(k)的个数

线段树已经去世,但是分块仍然可以胜任:

预先将每个块内数字排序,对于修改操作,显然不会影响整块的有序性,对于散块我们可以暴力重构,复杂度(O(sqrt{n}logn))

对于查询操作,可以散块暴力查询,整块二分查找,复杂度(O(sqrt{n}logn))

总复杂度(O(nsqrt{n}logn))

分块牺牲了部分复杂度,所以处理信息更加灵活,保留的信息量更多

入门例题

就放黄学长的分块入门吧

分块入门1:区间加,单点查询

没什么好说的了,放代码康康格式吧


展开查看

#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int read()
{
    int x=0,f=1;
    char ch;
    for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar());
    if(ch=='-') f=0,ch=getchar();
    while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
    return f?x:-x;
}
int n,cnt;
int a[100010];
int bel[100010],tag[100010];
inline void update(int l,int r,int k)
{
    int lx=bel[l],rx=bel[r];
    for(int i=l;bel[i]==lx&&i<=r;++i)
        a[i]+=k;
    if(lx==rx) return ;
    for(int i=r;bel[i]==rx;--i)
        a[i]+=k;
    for(int i=lx+1;i<rx;++i)
        tag[i]+=k;
}
signed main()
{
    n=read();
    cnt=sqrt(n);
    for(int i=1;i<=n;++i)
    {
        a[i]=read();
        bel[i]=(i-1)/cnt+1;
    }
    for(int t,x,y,z,i=1;i<=n;++i)
    {
        t=read(),x=read(),y=read(),z=read();
        if(!t)
            update(x,y,z);
        else
            printf("%lld
",a[y]+tag[bel[y]]);
    }
return 0;
}


分块入门2:区间加,区间小于(k)个数

刚才讲过惹(qwq)

分块入门3:区间加,区间(k)前驱

(emmm)和上一题方法一样,懒得讲了

分块入门4:区间加,区间求和

也讲过了……(这样看来这篇博客画风清奇

分块入门5:区间求和,区间开方(下取整)

这个有点意思

首先区间求和肯定要预处理出块内和,那对于区间开方怎么处理呢?

这个题目有个条件,(a_i<=maxint),我们可以算出对于每个数字最多开方(6)次就会变成(1)(0),然后再也不会变化了

所以如果一个块内全部变为了(1)或者(0),就不用再处理了,那么对于块内存在非(1)(0)的块,暴力重构,对于已经不会变化的块直接打(continue)标记然后跳过

分块入门6:单点插入,单点查询(数据随机)

考虑到数据随机,对每个块开个链表,记录下每个块内有目前多少个元素,然后模拟就好了

如果数据不随机怎么办?我们可以设置一个值s,每当插入s个数字就对整个序列重新分块,可以证明理论复杂度仍然是(O(nsqrt{n}))级别的

分块入门7:区间加法,区间乘法,区间求和

这不线段树(2)吗(雾

我在这里再说明一下标记顺序的问题:一定要先乘再加!

先加后乘效果如下:

假设当前状态为((a+add)*mul),又添加了一组([add2,mul2])标记

当前状态变为(((a+add)*mul+add2)*mul2)

展开(((a+add)*mul*mul2+add2*mul2))

继续展开((a*mul*mul2+add*mul*mul2+add2*mul2))

恢复标记格式:((a+add+(frac{add2}{mul}))*mul*mul2)

发现了什么?分数!这样可能会丢精度(qwq)

如果我们先乘后加呢

((a*mul+add)添加新标记)[add2,mul2]$

当前状态变为(((a*mul)+add)*mul2+add2)

展开((a*mul*mul2+add*mul2+add2))

恢复标记格式:((a*mul*mul2)+add*mul2+add2)

完美

注意细节:对散块处理之前要下放整个散块标记

分块入门8:查询区间等于(k)的个数,区间修改

似乎可以沿用分块入门(2)的套路,不过很不幸被卡了

考虑入门(5)的分析方法:对于非同一数字块暴力计算,对于同一数字块

……好像有点锅,先咕咕咕了

以上是关于分块入门的主要内容,如果未能解决你的问题,请参考以下文章

LiberOJ 6278 数列分块入门 2(分块)

loj#6281. 数列分块入门 5

Loj 6285. 数列分块入门 9

LibreOJ 6278 数列分块入门 2(分块区间加法,二分)

数列分块入门7 解题报告

数列分块入门9 解题报告