分块 学习笔记

分块是一种思想,指的是把数据划分为若干块,然后将块作为整体处理。此指数据结构中的块状数组。下文以维护区间加,区间查询为例。

初始化

一般来说,如果给出的数组长度为 \(n\),块数会取 \(\sqrt{n}\) 使块长与块数均衡。然后预处理块端点,每一个下标属于的块,块长与要维护的信息。末尾剩下的合并到最后一块。

void build(int n){
  bn=sqrt(n);
  for(int i=1;i<=bn;i++)bl[i]=n/bn*(i-1)+1,br[i]=n/bn*i;
  br[bn]=n;
  for(int i=1;i<=bn;i++)len[i]=br[i]-bl[i]+1;
  for(int i=1;i<=bn;i++)for(int j=bl[i];j<=br[i];j++)bp[j]=i,v[i]+=a[j];
}

区间加

当加的区间左右端点在同一个块时,直接修改原数组和每一块的和。

否则,先处理左右不完整的块,然后对中间完整的块打标记。

void add(int l,int r,long long x){
  if(bp[l]==bp[r])for(int i=l;i<=r;i++)a[i]+=x,v[bp[i]]+=x;
  else{
    for(int i=l;i<=br[bp[l]];i++)a[i]+=x,v[bp[i]]+=x;
    for(int i=bp[l]+1;i<bp[r];i++)tag[i]+=x;
    for(int i=bl[bp[r]];i<=r;i++)a[i]+=x,v[bp[i]]+=x;
  }
}

区间查询

同理,区间左右端点在同一个块时,直接统计答案,否则,先统计左右不完整的块,然后计算中间完整的块。

long long query(int l,int r,long long ans=0){
  if(bp[l]==bp[r])for(int i=l;i<=r;i++)ans+=(a[i]+tag[bp[i]]);
  else{
    for(int i=l;i<=br[bp[l]];i++)ans+=(a[i]+tag[bp[i]]);
    for(int i=bp[l]+1;i<bp[r];i++)ans+=(v[i]+tag[i]*len[i]);
    for(int i=bl[bp[r]];i<=r;i++)ans+=(a[i]+tag[bp[i]]);
  }
  return ans;
}

复杂度证明

假设 \(n,m\) 同阶。每次执行操作,左右的散块是根号级别的,中间完整的块也不超过根号级别。因此时间复杂度 \(O(n\sqrt{n})\),是根号算法。虽然复杂度逊于对数级算法,但是灵活性更高,常数不大。

一般情况下块数为 \(O(\sqrt{n})\),但有时可以微调块长。但是这就比较玄学了。

[[数据结构]]

posted @ 2024-03-01 09:29  lgh_2009  阅读(12)  评论(0)    收藏  举报