根號算法學習筆記

前言:根號算法太優雅踩爆線段樹頂呱呱(霧

前置芝士:根號平衡

  • \(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}\)git

1.分塊1:區間修改與單點查詢

#include <cstdio>
#include <cctype>
#include <cmath> 

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;
}
相關文章
相關標籤/搜索