PREFIXSUMCHAFENBITMO

Prefix sum

你們應該早就會了

$$a[n] = \sum_{i = 1}^{n} a[i]$$

他可以幹嘛

$$\sum_{i = l}^{r} a[i] = a[r] - a[l-1]$$

你可以 \(O(n)\)預處理
\(O(1)\)計算區間和

二維前綴和

$$a[n][m] = \sum_{i = 0}^{n} \sum_{j = 0}^{m} a[i][j]$$

藍色部分的區間和

\(=a[y_1][x_1]-a[y_1][x_0]-a[y_0][x_1]+a[y_0][x_0]\)

注意邊界 \(x_0\) 和 \(y_0\) 不包含在我們要查的範圍

題目

difference array

定義

\(d[n] = a[n] - a[n-1]\)

他可以幹嘛

區間改值時可以\(O(1)\)修改

查詢的方法是跑一次前綴和 (\(O(n)\)?)

BIT

不是比特

區間改值+單點查詢

我可以用前綴何來處理區間改值

可是單點查詢要\(O(n)\)

binary indexed tree (Fenwick Tree)

這是一個樹狀資節

最上面會有一個根節點

一個根節點會有一些子節點往下延伸

一個子節點只能有一個父節點

最底下的節點叫葉節點

lowbit

對於一個二進位數字 \(n\)

\(lowbit(n)\)就是把不是最低位的1改成0

例如\(lowbit(0001111000)=1000\)

如何在C++取\(lowbit\)呢?

跟 \(n\) 的負數取bitsiwe and

n-nlowbit(n), n&-n
10011(01100+1)=0110100001
10100(01011+1)=0110000100
10000(01111+1)=1000010000

概念

首先忽略根節點,他不會用到,BIT是1-based

然後你會很容易地觀察到,每個節點存的區間右界,就是他自己

那區間大小呢?

注意到其實就是 \(lowbit(index)\)

然後一個節點的父節點 \(index\)

就是自己的 \(index\) 減掉 \(lowbit(index)\)

區間查詢

例如說查詢\(\sum_{i=3}^{10}a[i]\)

相當於\(\sum_{i=1}^{10}a[i]-\sum_{i=1}^{2}a[i]\)

而a[1]~a[10]的總和

其實就是從節點10開始往父節點跑

然後把經過節點的值加起來

a[1]~a[2]也是同理

單點改值

例如說修改 a[5]

那我需要修改所有涵蓋到 a[5] 的節點

如果我做一個樹,其父節點的區間會涵蓋子節點的區間的話

你會發現父節點的\(index\)

就是子節點的\(index+lowbit(index)\)

所以你就會了

題目

區間修改+單點查詢

BIT存差分

區間修改相當於兩次單點改值

單點修改相當於區間查詢

區間修改+區間查詢

a是原陣列,d是差分陣列

$$\sum_{i=0}^{n-1}a[i] = \sum_{i=0}^{n-1}\sum_{j=0}^{i}d[j]=d[0]\times x + d[1] \times (x - 1)+ d[2] \times (x - 2)......$$

$$=\sum_{i=0}^{n-1}d[i] \times (n - i) = n \times (\sum_{i = 0}^{n - 1}d[i]) - (\sum_{i = 0}^{n - 1}d[i] \times i)$$

從 n 次的區間查詢變成 2 次的區間查詢

所以用 BIT存 \(d[i]\) 與 \(d[i] \times i\)

然後你就會了

二維BIT

概念一模一樣

for (int i = x; i > 0; i -= lowbit(i)) {
        for (int j = y; j > 0; j -= lowbit(j)) {
            res += BIT[i][j];
    }
}
for (int i = x; i > 0; i -= lowbit(i)) {
    res += BIT[i];
}

一維

二維

for (int i = x; i <= N; i += lowbit(i)) {
    BIT[i] += d;
}
for (int i = x; i <= N; i += lowbit(i)) {
        for (int j = y; j <= M; j += lowbit(j)) {
            BIT[i][j] += d;
    }
}

題目

MO's algorithm

有人分塊中毒了

分塊

偷顏子喬簡報

分塊

複雜度 \(O(\frac{N}{K}+K)\)   K 是塊大小

根據算幾不等式 取 \(K = \sqrt{N}\)

莫隊

如果我已知 [L, R] 區間上的答案

而且走到 [L-1, R] [L+1, R] [L, R-1] [L, R+1] 只要 \(O(1)\)

那麼我可以 \(O(N \sqrt{N} )\) 把整題做完

怎做?

分塊

先把所有詢問讀下來

按照 L 分塊

接著按照 R 排序

因為 L 只會在塊內動而且 R 有單調性

加起來不超過 \(O(N\sqrt{N})\)

莫隊

sort(qs.begin(), qs.end(), [&](const Query &x, const Query &y) {
    if (x.l / B == y.l / B)
        return x.r < y.r;
    return (x.l / B) < (y.l / B);
});
for (auto &qr : qs) {
    int l = qr.l, r = qr.r;

    while (R < r) add(++R);
    while (L > l) add(--L);
    while (R > r) remove(R--);
    while (L < l) remove(L++);

    ans[qr.id] = cur;
}

排序

轉移

奇偶優化

bool comp(const Query &x, const Query &y){
    if(x.l / K != y.l / K) return x.l / K < y.l / K;
    return ((x.l / K) % 2 == 0)? x.r < y.r : x.r > y.r;
}

偶數塊右界掃過去 奇數塊右界掃回來

一般情况下,这种优化能让程序快 30% 左右.

莫隊

這題明明可以 O(N) 誰要寫莫隊

有趣題目

下一頁有暴雷 但是官解的圖好帥

prefixsumchafenbitmo

By Roy Chuang

prefixsumchafenbitmo

  • 26