根号分块

根号分块


分块是一种借助了线段树的序列区间化+懒标记的思想。

  • 区间序列化:将序列分为若干个等长的块,每一块有一段管理区间,用于存储该区间的某些信息,如区间之和、区间之积、可重复贡献性问题(区间最值、区间GCD)等。
    设序列长度为 n n n,块长为 b b b,则可分为 n b \frac{n}{b} bn块。最后一块可能是并非完整的碎片块。
  • 分块 i i i的管理区间 [ L , R ] [L,R] [L,R] L = ( i − 1 ) × b + 1 L=(i-1)\times b+1 L=(i1)×b+1(上个分块 R + 1 R+1 R+1); R = { i × b (整块) n (碎块) R={i×b(整块)n(碎块) R={i×bn(整块)(碎块)
  • 对于元素 i i i,设其位于第 k k k个分块上:由分块管理区间可知 ( k − 1 ) × b + 1 ≤ i ≤ k × b (k-1)\times b+1\le i\le k\times b (k1)×b+1ik×b,移项得 k − 1 ≤ i − 1 b , i − 1 b < i b ≤ k ⇒ k − 1 ≤ i − 1 b < k ⇒ k − 1 = ⌊ i − 1 b ⌋ k-1\le \frac{i-1}{b},\frac{i-1}{b}<\frac{i}{b}\le k\Rightarrow k-1\le \frac{i-1}{b}<k\Rightarrow k-1=\lfloor\frac{i-1}{b}\rfloor k1bi1,bi1<bikk1bi1<kk1=bi1,故 k = ⌊ i − 1 b ⌋ + 1 k=\lfloor\frac{i-1}{b}\rfloor+1 k=bi1+1
  • 懒标记:当涉及区间的统一操作时,不逐一修改每个元素,而是在区间所属的块上打上懒标记,进行统一的修改

如何分块?设操作区间为 [ L , R ] [L,R] [L,R]

  • [ L , R ] [L,R] [L,R]在一个整块内,称为只与一个区块有交,直接遍历 [ L , R ] [L,R] [L,R]操作即可,复杂度最多为 O ( b ) O(b) O(b)
  • [ L , R ] [L,R] [L,R]不在一个整块内,称为与多个块有交,则操作由三部分拼接:以 L L L起始的后缀碎块,中间整块,以 R R R结尾的前缀碎块。处理方法为整块打包维护,碎块逐个枚举:对于碎块,由于存在元素 ∉ [ L , R ] \notin[L,R] /[L,R],因此需要逐个遍历碎块中 ∈ [ L , R ] \in[L,R] [L,R]的元素进行操作;对于整块,则直接调用其管理区间的信息+懒标记进行整体操作。最坏情况下 [ L , R ] [L,R] [L,R]近似为序列长度,复杂度 O ( n b + b ) O(\frac{n}{b}+b) O(bn+b)
  • 分块长度应使 n b + b \frac{n}{b}+b bn+b最小化。根据基本不等式, n b + b ≥ 2 n \frac{n}{b}+b\ge2\sqrt{n} bn+b2n 。因此分块长度一般为 n \sqrt n n 为最优解,因此可在 O ( q n ) O(q\sqrt{n}) O(qn )的复杂度完成区间修改+区间查询问题。以 n \sqrt{n} n 长度进行分块的方法称为根号分块法,是最简单的分块方法。
//下标均从1开始
int n,len=sqrt(n),num=ceil(n/(float)len);//若有碎块需+1
struct node{
    int data,pos;//数据,所在区块
}a[n+1];
struct block{
    int l,r,data,tag;//块的管理区间[l,r],区间信息,懒标记
}b[num+1];
void init(){
    for(int i=1;i<=num;i++) b[i].l=(i-1)*len+1,b[i].r=i*len;
    b[num].r=n;//重新计算碎块管理区间
    for(int i=1;i<=n;i++) a[i].pos=(i-1)/len+1;//获取第i个下标所属的块
    for(int i=1;i<=num;i++)//遍历所有分块
        for(int j=b[i].l;j<=b[i].r;j++)//遍历块内所有元素
            b[i].data=opt(b[i].data,a[j]);
}

由于只遍历了原序列一次,因此预处理复杂度为 O ( n ) O(n) O(n)

区间修改

void modify(int l,int r,int d){//以[l,r]均+d为例
    int pl=a[l].pos,pr=a[r].pos;
    if(pl==pr){//l,r在相同块中
        for(int i=l;i<=r;i++) a[i].data+=d;
        b[pl].data+=(r-l+1)*d;
    }else{
        //处理l所在碎片
        for(int i=l;i<=b[pl].r;i++) a[i].data+=d;
        b[pl].data+=(b[pl].r-l+1)*d;
        //处理中间的整块
        for(int i=pl+1;i<pr;i++) b[i].tag+=d;
        //处理r所在碎片
        for(int i=b[pr].l;i<=r;i++) a[i].data+=d;
        b[pr].data+=(r-b[pr].l+1)*d;
    }
}

区间查询

int query(int l,int r){//以查询[l,r]之和为例
    int pl=a[l].pos,pr=a[r].pos,ans=0;
    if(pl==pr){
        for(int i=l;i<=r;i++) ans+=a[i].data;
        ans+=b[pl].tag*(r-l+1);//加上懒标记
    }else{
        //处理l所在碎片
     	for(int i=l;i<=b[pl].r;i++) ans+=a[i].data;
        ans+=b[pl].tag*(b[pl].r-l+1);
        //处理中间的整块
        for(int i=pl+1;i<pr;i++) ans+=b[i].data+b[i].tag*(b[i].r-b[i].l+1);
        //处理r所在碎片
        for(int i=b[pr].l;i<=r;i++) ans+=a[i].data;
        ans+=b[pr].tag*(r-b[pr].l+1);
    }
    return ans;
}

扩展:分块求解静态RMQ问题的其他方法

由于问题为静态RMQ,则只有查询没有修改,一旦确定区块最值后,其将不再改变。

设序列长度为 n n n,分块长度为 b b b,序列被分为 n b \frac{n}{b} bn块,有 q q q次询问。若操作区间 [ L , R ] [L,R] [L,R]与多个块有交,则被分为以 L L L起始的后缀碎块、中间若干个整块、以 R R R结尾的前缀碎块3个部分。对于中间若干个区块的整体最值,其由若干个区块最值拼接而成。

预处理

  • 块间预处理:双指针依次遍历所有区块,预处理出区块 i i i到区块 j ( i , j ∈ [ 1 , n b ] , i < j ) j(i,j\in[1,\frac{n}{b}],i<j) j(i,j[1,bn],i<j)的最值。复杂度 O ( ( n b ) 2 ) O((\frac{n}{b})^2) O((bn)2)。此预处理是为能处理区块拼接成中间整块的最值查询。
  • 块内预处理:依次枚举每个块,分别计算出块内每个元素的前缀和后缀元素最值。复杂度 O ( n ) O(n) O(n)。此预处理是为了能处理前后缀碎块的最值查询。

预处理的复杂度 O ( ( n b ) 2 ) O((\frac{n}{b})^2) O((bn)2)

查询

  • 若询问 [ L , R ] [L,R] [L,R]与多个区块有交,复杂度 O ( 1 ) O(1) O(1);否则复杂度 O ( b ) O(b) O(b)

查询的复杂度 O ( q × b ) O(q\times b) O(q×b)

分块长度分析

以上步骤整体复杂度 O ( ( n b ) 2 + q × b ) O((\frac{n}{b})^2+q\times b) O((bn)2+q×b)

根据基本不等式可知 ( n b ) 2 + q × b ≥ 2 n 2 × q b (\frac{n}{b})^2+q\times b\ge2\sqrt{\frac{n^2\times q}{b}} (bn)2+q×b2bn2×q ,当 ( n b ) 2 = q × b (\frac{n}{b})^2=q\times b (bn)2=q×b 时复杂度取最小值。

b = n 2 3 q 3 b=\frac{\sqrt[3]{n^2}}{\sqrt[3]{q}} b=3q 3n2 ,复杂度 O ( ( n × q ) 2 3 ) O(\sqrt[3]{(n\times q)^2}) O(3(n×q)2 )

posted @   椰萝Yerosius  阅读(3)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
点击右上角即可分享
微信分享提示