根号算法学习笔记

前言:根号算法太优雅踩爆线段树顶呱呱(雾

前置芝士:根号平衡

  • \(x\)次操作,单次复杂度为\(O(a)\)
  • \(y = kx\)次操作,单次复杂度为\(O(b)\)
  • 在满足一定条件的题里面,可以通过提高其中一边的复杂度,降低另一边的复杂度来达到更好的效果,这就是根号平衡
  • \(E.g.:\)
    带修改区间和问题的部分做法
    如果采用\(sqrt(n)\)叉树,即有\(2\)
    如果采用\(2\)叉树,即有\(logn\)
    第一个是分块,第二个是线段树
    这个即是一个根号平衡的例子。
    ——lxl

分块:

将一个长度为\(n\)的序列分为若干块,每个块长度为\(m\),则共有\(n/m\)块。
整块:区间操作时完整地包含于区间的块。
不完整的块:在一个区间操作时,只有部分包含于区间的块,即区间左右端点所在的两个块。
对于整块,我们可以直接\(O(1)\)的打标记,最后查询时只需要将原来的值加上标记值即可。
对于不完整的块,因为数量较少,直接暴力修改。
这样每次操作的复杂度是\(O(n/m)+O(m)\),根据均值不等式,当\(m\)\(\sqrt{n}\)时总复杂度最低,为了方便,我们都默认下文的分块大小为\(\sqrt{n}\)

1.分块1:区间修改与单点查询

#include 
#include 
#include  

int tag[50010], a[50010], blo[50010], n, s;//tag为标记,a为原值,blo记录每个点属于哪个块

inline int min(int a, int b) { return a < b ? a : b; }
inline int read() {
    int s = 1, w = 0; char ch = getchar();
    for(; ! isdigit(ch); ch = getchar()) if(ch == '-') s = -1;
    for(; isdigit(ch); ch = getchar()) w = w * 10 + ch - '0';
    return s * w;
}
inline void modify(int l, int r, int c) {
    for(int i = l; i <= min(r, s * blo[l]); i ++) a[i] += c;//左端点所在的不完整的块暴力修改
    if(blo[l] != blo[r]) //如果l和r不在同一块则暴力修改,否则在上一步已经修改完成
      for(int i = (blo[r] - 1) * s + 1; i <= r; i ++) //暴力修改右端点所在的不完整块 
        a[i] += c;
    for(int i = blo[l] + 1; i <= blo[r] - 1; i ++)//O(1)整块打标记
      tag[i] += c;
}

int main() {
    n = read(), s = sqrt(n);
    for(int i = 1; i <= n; i ++) a[i] = read();
    for(int i = 1; i <= n; i ++) blo[i] = (i - 1) / s + 1;//记录每个点属于哪个块
    for(int i = 1; i <= n; i ++) {
      int opt = read(), l = read(), r = read(), c = read();
      if(opt == 0) modify(l, r, c);//区间修改
      else printf("%d\n", a[r] + tag[blo[r]]);//单点查询    
    }
    return 0;
}

你可能感兴趣的:(根号算法学习笔记)