樹狀數組

樹狀數組

1. 算法分析

樹狀數組做用c++

  1. 單點修改
  2. 區間查詢
  3. 區間修改(加上差分)
    批註 2020-06-08 001350.png

核心思想
    把前n個數劃分爲log(n)個區間,分別維護這log(n)個區間的和,在求解前綴和Sn的時候,從求解n個數字的和變成求解log(n)個區間的和來加快運算算法

具體操做
    維護log(n)個區間,每一個區間用數組c來維護區間和。單點修改x的時候,修改x所在的c,而後一路向上修改父節點的c;區間查詢[1 ~ x]的時候,從x開始向前,把x的全部兄弟節點的c都加起來數組

c數組性質網絡

  1. c[x]數組維護區間[x - lowbit(x) + 1, x]的和
  2. c[x]數組維護的區間的長度爲lowbit(x)
  3. c[x]的父節點爲 c[x + lowbit(x)]
  4. c[x]前一個兄弟節點爲 c[x - lowbit(x)]

2. 板子

2.1 一維樹狀數組:單點修改+區間查詢

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
int const N = 5e5 + 10;
int a[N], n, m;
LL c[N], sum[N];  // c[x] = a[x-lowbit(x) + 1] + ... + a[x],sum[x]=a[1]+...+a[x]

int lowbit(int x) {
    return x & (-x);
}

// 單點修改
void add(int x, int y) {
    for (int i = x; i <= n; i += lowbit(i)) c[i] += y;  // 不斷往父節點跳
}

// 查詢Sx前綴和
LL query(int x) {
    LL res = 0;
    for (int i = x; i; i -= lowbit(i)) res += c[i];  // 不斷往前一個兄弟節點跳
    return res;
}

int main() {
    cin >> n >> m;
    for (int i = 1; i <= n; ++i) {
        scanf("%d", &a[i]);  // 讀入每一個元素
        sum[i] = sum[i - 1] + a[i];  // 計算前綴和
    }
    
    for (int i = 1; i <= n; ++i) c[i] = sum[i] - sum[i - lowbit(i)];  // 初始化c數組
    
    for (int i = 1, op, x, y; i <= m; ++i) {  // m個操做
        scanf("%d%d%d", &op, &x, &y);  
        if (op == 1) add(x, y);   // 單點修改
        else printf("%lld\n", query(y) - query(x - 1));  // 區間查詢
    }
    
    return 0;
}

2.2 一維樹狀數組:區間修改+單點查詢

/*
本題要進行的是 區間增長+單點詢問,而樹狀數組能進行的操做爲 單點增長 + 區間查詢
能夠經過差分來實現這個轉變,使得樹狀數組能夠完成區間增長 + 單點詢問
即:維護一個b數組表示當前a數組的增長減小狀況,一旦a數組[l, r]增長d, 那麼b[l] += d, b[r + 1] -= d
所以咱們能夠用樹狀數組維護這個b數組的前綴和,即維護一個c數組,記錄b數組的前綴和,那麼經過add()操做就能改變前綴和
然後每次詢問單點時,咱們查詢b數組的前綴和就能夠知道a點的增長減小狀況
*/


#include<bits/stdc++.h>
using namespace std;

typedef long long LL;

int n, m;
int const N = 1e5 + 10;
int c[N], a[N];  // c[x]維護的是b數組的前綴和

int lowbit(int x) {
    return x & -x;
}

// 單點修改
void add(int x, int y) {
    for (int i = x; i <= n; i += lowbit(i)) c[i] += y;  // 不斷往父節點跳
}

// 查詢Sx前綴和
LL query(int x) {
    LL res = 0;
    for (int i = x; i; i -= lowbit(i)) res += c[i];  // 不斷往前一個兄弟節點跳
    return res;
}

int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);  // 讀入a[i]
    while (m--) {
        string op;
        int l, r, d, x;
        cin >> op;

        // 單點查詢
        if (op[0] == 'Q') {
            scanf("%d", &x);
            printf("%lld\n", query(x) + a[x]);  // x點的變化值+a[x]
        }
        else {
            scanf("%d %d %d", &l, &r, &d);
            add(l, d), add(r + 1, -d);  // 區間增長
        }
    }
    return 0;
}

2.3 一維樹狀數組:區間修改+區間查詢

/*
對於第一種處理方式,能夠利用差分結合acwing242一個簡單的整數問題1的方式來處理;
對於問題而求區間和,那麼考慮維護a的前綴和
a1+a2+...+ax=(b1)+(b1+b2)+(b1+b2+b3)+...+(b1+b2+b3+..+bx)
=(1+x)累加從1到x(bi)-累加從1到x(i*bi)
咱們能夠使用兩個樹狀數組分別維護bi和i*bi的前綴和
*/
#include<bits/stdc++.h>
using namespace std;

typedef long long LL;

int n, m;
int const N = 1e5 + 10;
LL c1[N], c2[N];  // c1維護bi的前綴和,c2維護i*bi的前綴和
int a[N];
LL s[N];

int lowbit(int x) {
    return x & -x;
}

// 單點修改
void add(LL c[], int x, int y) {
    for (int i = x; i <= n; i += lowbit(i)) c[i] += (LL)y;
}

// 前綴和
LL query(LL c[], int x) {
    LL res = 0;
    for (int i = x; i; i -= lowbit(i)) res += c[i];
    return res;
}

int main() {
    cin >> n >> m;
    for (int i = 1; i <= n; ++i) {
        scanf("%d", &a[i]);
        s[i] = s[i - 1] + 0ll + a[i];  // 計算原來的a[i]的前綴和
    }
    while (m--) {
        string op;
        int l, r, d;
        cin >> op;
        if (op[0] == 'C') {
            cin >> l >> r >> d;
            add(c1, l, d), add(c1, r + 1, -d);  // 把[l, r]加d對c1來講就是把l和r+1分別增長d和減去d
            add(c2, l, l * d), add(c2, r + 1, -(r + 1) * d);  // 把[l, r]加d對c1來講就是把l和r+1分別增長l*d和減去(r+1)*d
        }
        else {
            cin >> l >> r;
            LL res = s[r] - s[l - 1];  // 原來的區間和
            res += ((1 + r) * query(c1, r) - query(c2, r) - (l * query(c1, l - 1) - query(c2, l - 1)));  // 加上改變值
            printf("%lld\n", res );
        }
    }
    return 0;
}

2.4 二維樹狀數組:單點修改+區間查詢

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;

int const N = (1 << 12) + 10;
LL n, m, c[N][N];  // c[x][y] = 累加c[i][j], i∈(i-lowbit(i)+1, x), j∈(j-lowbit(j)+1, y) 

int lowbit(int x) {
    return x & -x;
}

// 單點修改
void add(int x, int y, int z) {
    for (int i = x; i <= n; i += lowbit(i)) 
        for (int j = y; j <= m; j += lowbit(j)) 
            c[i][j] += (LL)z;
}

// 區間查詢
LL query(int x, int y) {
    LL res = 0;
    for (int i = x; i; i -= lowbit(i)) 
        for (int j = y; j; j -= lowbit(j)) 
            res += c[i][j];
    return res;
}

int main() {
    cin >> n >> m;
    int op, x1, y1, k, x2, y2;
    while (scanf("%d", &op) != EOF) {
        if (op == 1) {  // 單點修改,把(x1, y1)加上k
            scanf("%d%d%d", &x1, &y1, &k);
            add(x1, y1, k);  
        }
        else {  // 區間查詢,獲得以左上角爲(x1, y1),右下角爲(x2, y2)的矩形的區間和
            scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
            printf("%lld\n", query(x2, y2) - query(x2, y1 - 1) - query(x1 - 1, y2) + query(x1 - 1, y1 - 1));
        }
    }
    return 0;
}

2.5 二維樹狀數組:區間修改+單點查詢

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
int const N = (1 << 12) + 10;
LL c[N][N], n, m, op;  // c[x][y]維護b[i][j]的前綴和

int lowbit(int x) {
    return x & (-x);
}

// 二維單點修改
void add(int x, int y, int z) {
    for (int i = x; i <= n; i += lowbit(i)) 
        for (int j = y; j <= m; j += lowbit(j))
            c[i][j] += z;
}

// 求二維前綴和
LL query(int x, int y) {
    LL res = 0;
    for (int i = x; i; i -= lowbit(i))
        for (int j = y; j; j -= lowbit(j))
            res += c[i][j];
    return res;
}

int main() {
    cin >> n >> m;
    while (scanf("%lld", &op) != EOF) {
        int x1, y1, k, x2, y2;
        if (op == 1) {  // 區間增長:轉化爲二維差分數組的增長
            scanf("%d%d%d%d%d", &x1, &y1, &x2, &y2, & k);
            add(x2 + 1, y2 + 1, k);
            add(x1, y1, k);
            add(x2 + 1, y1, -k);
            add(x1, y2 + 1, -k);
        }
        else {  // 單點查詢:轉化爲二維差分數組求前綴和
            scanf("%d%d", &x1, &y1);
            printf("%lld\n", query(x1, y1));
        }
    }
    return 0;
}

2.6 二維樹狀數組:區間修改+區間查詢

/*
累加aij(i∈[1, x], j∈[1, y])=(x+1)*(y+1)累加bij - (x+1)累加j*bij - (y + 1)累加i*bij + 累加i*j*bij
c1維護bij的二維前綴和,c2維護j*bij的二維前綴和,c3維護i*bij的二維前綴和,c4維護i*j*bij的二維前綴和
*/
#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
int const N = (1 << 12) + 10;
LL c1[N][N], c2[N][N], c3[N][N], c4[N][N], n, m, op;  // c[x][y]維護b[i][j]的前綴和

int lowbit(int x) {
    return x & (-x);
}

// 二維單點修改
void add(LL c[][N], int x, int y, LL z) {
    for (int i = x; i <= n; i += lowbit(i)) 
        for (int j = y; j <= m; j += lowbit(j))
            c[i][j] += z;
}

// 求二維前綴和
LL query(LL c[][N], int x, int y) {
    LL res = 0;
    for (int i = x; i; i -= lowbit(i))
        for (int j = y; j; j -= lowbit(j))
            res += c[i][j];
    return res;
}

LL Query(int x, int y) {
    return (x + 1) * (y + 1) * query(c1, x, y) - (x + 1) * query(c2, x, y) - (y + 1) * query(c3, x, y) + query(c4, x, y);
}

int main() {
    cin >> n >> m;
    while (scanf("%lld", &op) != EOF) {
        int x1, y1, x2, y2, k;
        if (op == 1) {
            scanf("%d%d%d%d%d", &x1, &y1, &x2, &y2, &k);
            add(c1, x2 + 1, y2 + 1, k), add(c1, x1, y1, k), add(c1, x2 + 1, y1, -k), add(c1, x1, y2 + 1, -k);
            add(c2, x2 + 1, y2 + 1, k * (y2 + 1)), add(c2, x1, y1, k * y1), add(c2, x2 + 1, y1, -k * y1), add(c2, x1, y2 + 1, -k * (y2 + 1));
            add(c3, x2 + 1, y2 + 1, k * (x2 + 1)), add(c3, x1, y1, k * x1), add(c3, x2 + 1, y1, -k * (x2 + 1)), add(c3, x1, y2 + 1, -k * x1);
            add(c4, x2 + 1, y2 + 1, k * (x2 + 1) * (y2 + 1)), add(c4, x1, y1, k * x1 * y1), add(c4, x2 + 1, y1, -k * (x2 + 1) * y1), add(c4, x1, y2 + 1, -k * x1 * (y2 + 1));
        }
        else {
            scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
            printf("%lld\n", Query(x2, y2) + Query(x1 - 1, y1 - 1) - Query(x1 - 1, y2) - Query(x2, y1 - 1));
        }
    }
    return 0;
}

3. 例題

3.1 樹狀數組+逆序對

POJ2299 Ultra-QuickSort
有多個測試樣例,每一個測試樣例給定n個數字a[i], 求這n個數字的逆序對數目
n~5e5, a[i]~999999999測試

// 使用樹狀數組去維護每一個數字出現的次數的前綴和,c[i]記錄i這個節點管轄的幾個點的出現次數
// 統計時,只要統計每一個點右邊有多少個比他小便可
// 注意須要離散化
#include <bits/stdc++.h>

using namespace std;
typedef long long LL;
int const N = 5e5 + 10;
LL c[N], n, a[N];
vector<LL> v;
unordered_map<LL, int> H;

int lowbit(int x) {
    return x & -x;
}

void add(int x, int y) {
    for (int i = x; i <= n; i += lowbit(i)) c[i] += y;
}

LL query(int x) {
    LL res = 0;
    for (int i = x; i; i -= lowbit(i)) res += c[i];
    return res;
}

int main() {
    while (scanf("%lld", &n) != EOF && n) {
        memset(c, 0, sizeof c);
        v.clear();
        H.clear();
        for (int i = 1; i <= n; ++i) {
            scanf("%lld", &a[i]);
            v.push_back(a[i]);
        }
        sort(v.begin(), v.end());
        int cnt = 1;
        for (int i = 0; i < v.size(); ++i) H[v[i]] = cnt++;  // 離散化
        LL res = 0;
        for (int i = n; i >= 1; --i) {  // 計算每一個H[a[i]出現的次數
            res += query(H[a[i]] - 1);  // 加上H[a[i]-1出現的總次數
            add(H[a[i]], 1);  // 次數加一
        }
        printf("%lld\n", res);
    }
    return 0;
}

acwing241樓蘭圖騰
有n個點,座標分別爲(1, y1), (2, y2), (3, y3), ..., (n, yn)
若是三個點(i, yi), (j, yj), (k, yk) 知足1 ≤ i < j < k ≤ n且yi > yj, yj < yk,則稱這三個點構成V圖騰;
若是三個點(i, yi), (j, yj), (k, yk)知足1 ≤ i < j < k ≤ n 且yi < yj, yj > yk,則稱這三個點構成∧圖騰;
求有多少個V圖騰和∧圖騰
n ~ 2e5, yi ~ 2e5ui

/*
使用樹狀數組去維護每一個數字出現的次數的前綴和,c[i]記錄i這個節點管轄的幾個點的出現次數
統計時,只要統計每一個點左邊有多少個比他大,右邊有多少個比他大,而後相乘就可以知道出現多少個^;
統計V則相反
*/
#include<bits/stdc++.h>
using namespace std;

typedef long long LL;

int n;
int const N = 2e5 + 10;
int a[N], c[N];
int gre[N], low[N];

int lowbit(int x) {
    return x & -x;
}

void add(int x, int y) {
    for (int i = x; i <= n; i += lowbit(i)) c[i] += y;
}

int query(int x) {
    int res = 0;
    for (int i = x; i; i -= lowbit(i)) res += c[i];
    return res;
}

int main() {
    cin >> n;
    for (int i = 0; i < n; ++i) scanf("%d", &a[i]);
    LL res1 = 0, res2 = 0;
    for (int i = 0; i < n; ++i) {
        int y = a[i];
        gre[i] = query(n) - query(y);
        low[i] = query(y - 1);
        add(y , 1);
    }

    memset(c, 0, sizeof c);
    for (int i = n - 1; i >= 0; --i) {
        int y = a[i];
        res1 += (LL)gre[i] * (query(n) - query(y));
        res2 += (LL)low[i] * query(y - 1);
        add(y , 1);
    }
    cout << res1 << " " << res2 << endl;
    return 0;
}

POJ3067 Japan
有兩個海岸,兩個海岸分別有N個點和M個點,從北到南編號爲1,2,3...,N。如今在兩個海岸之間建設橋樑。每一個橋樑給出x, y,表示第一個海岸的x點到第二個海岸的y點有橋樑。問造成交叉的橋樑數目有多少。
N、M~1e3spa

// 設橋樑1爲(x1, y1),橋樑2爲(x2, y2),當(x1-x2)*(y1-y2)<0時兩個橋樑交叉
// 所以只須要把y按照從大到小的順序排序,每次觀察小於當前x的數有多少個便可
// 這樣就轉化爲求解逆序對的問題,樹狀數組求之
#include<bits/stdc++.h>
using namespace std;

typedef long long LL;

int n, m, T, q, kase = 1;
int const N = 1e3 + 10;
LL c[N];
struct Line {
    int x, y;
    bool operator<(const struct Line &w) const {
        if (y == w.y) return x > w.x;
        else return y > w.y;
    }
}line[N * N];

LL lowbit(LL x) {
    return x & -x;
}

// 單點修改
void add(int x, LL y) {
    for (int i = x; i <= N; i += lowbit(i)) c[i] += y;  // 不斷往父節點跳
}

// 查詢Sx前綴和
LL query(int x) {
    LL res = 0;
    for (int i = x; i; i -= lowbit(i)) res += c[i];  // 不斷往前一個兄弟節點跳
    return res;
}

int main()
{
    cin >> T;
    while (T--) {
        cin >> n >> m >> q;
        memset(c, 0, sizeof c);
        for (int i = 1; i <= q; ++i) scanf("%d%d", &line[i].x, &line[i].y);
        sort(line + 1, line + 1 + q);  // 按y從大到小排序
        
        LL res = 0;
        for (int i = 1; i <= q; ++i) {
            res += query(line[i].x - 1);  // 計算小於當前x的數目
            add(line[i].x, 1);  // 當前x加一
        }
        printf("Test case %d: %lld\n", kase++, res);
    }
    return 0;
}

POJ 2352 Stars
給一個二維網格,而後給定網格上n個點,計算每一個點的左下方的點個數
點的輸入是y從小到大輸入,若是y相同,那麼x從小到大輸入。
n~15000, x、y~32000code

// 題目順序都處理好了。注意向右平移一個單位,由於0號位置也有star
#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
int const N = 2e5 + 10;
int n;
LL c[N], res[N];

int lowbit(int x) {
    return x & (-x);
}

// 單點修改
void add(int x, int y) {
    for (int i = x; i <= N; i += lowbit(i)) c[i] += y;  // 不斷往父節點跳
}

// 查詢Sx前綴和
LL query(int x) {
    LL res = 0;
    for (int i = x; i; i -= lowbit(i)) res += c[i];  // 不斷往前一個兄弟節點跳
    return res;
}

int main() {
    cin >> n;
    for (int i = 1, x, y; i <= n; ++i) {
        scanf("%d%d", &x, &y);
        x++;  // 樹狀數組不能統計0
        res[query(x)]++;  // 計數
        add(x, 1);  //x的次數加一
    }
    for (int i = 0; i <= n - 1; ++i) printf("%lld\n", res[i]);
    
    return 0;
}

3.2 樹狀數組+思惟

acwing244謎同樣的牛
有n頭奶牛,已知它們的身高爲 1~n 且各不相同,但不知道每頭奶牛的具體身高。
如今這n頭奶牛站成一列,已知第i頭牛前面有Ai頭牛比它低,求每頭奶牛的身高。
1≤n≤105排序

/*
本題能夠從n往1向前看,若是他的前面有a[i]頭牛比他低,那第i頭牛的高度就是能夠選擇的身高的第i+1小。
所以能夠給每一個身高一個標值,1表示沒被選,0表示被選中。那麼尋找第a[i]+1個沒被選中的牛就能夠二分查找
前綴和等於a[i]+1的那個數字,樹狀數組維護這個前綴和便可
*/
#include<bits/stdc++.h>

using namespace std;

int n;
int const N = 1e5 + 10;
int c[N], a[N], ans[N];

int lowbit(int x) {
    return x & -x;
}

void add(int x, int y) {
    for (int i = x; i <= n; i += lowbit(i)) c[i] += y;
}

int query(int x) {
    int res = 0;
    for (int i = x; i; i -= lowbit(i)) res += c[i];
    return res;
}

int main()
{
    cin >> n;
    for (int i = 2; i <= n; ++i) scanf("%d", &a[i]);

    for (int i = 1; i <= n; ++i) c[i] = lowbit(i);  // 初始化,由於每一個身高都沒備選,那麼都初始化爲1,Tr數組就是區間的長度,即爲lowbit(i)

    for (int i = n; i >= 1; --i)   // 倒着往前看
    {
        // 二分查找a[i]+1小
        int l = 1, r = n;  
        while (l < r)
        {
            int mid = (l + r) >> 1;
            if (query(mid) >= a[i] + 1) r = mid;
            else l = mid + 1;
        }
        ans[i] = l;  // 記錄答案 
        add(l, -1);  // 這個牛被選了,從1變成0
    }
    for (int i = 1; i <= n; ++i) printf("%d\n", ans[i]);
    return 0;
}

acwing260買票
給定n,表示遊客的數目;隨後n行,每行兩個數,p[i]和v[i],分別表示當這個遊客插隊後,前面的人數以及這個遊客的編號。求出當所有遊客完成插隊後,隊列中的遊客編號狀況。
n ~ 2e5,P[i]、V[i] ~ short隊列

// 本題的思路和acwing244同樣,倒着往前看,插入前面人數+1的位置。
#include <bits/stdc++.h>

using namespace std;

int const N = 2e5 + 10;
int res[N], P[N], V[N], n, c[N];

typedef long long LL;

int lowbit(int x) {
    return x & -x;
}

void add(int x, int y) {
    for (int i = x; i <= n; i += lowbit(i)) c[i] += y;
}

int query(int x) {
    int res = 0;
    for (int i = x; i; i -= lowbit(i)) res += c[i];
    return res;
}

int main() {
    while (scanf("%d", &n) != EOF) {
        for (int i = 1; i <= n; ++i) {
            scanf("%d%d", &P[i], &V[i]);
            c[i] = lowbit(i);
        }
        for (int i = n; i >= 1; --i) {
            int pos = P[i] + 1;
            int l = 1, r = n;
            while (l < r) {
                int mid = l + r >> 1;
                if (query(mid) >= pos) r = mid;
                else l = mid + 1;
            }
            res[l] = V[i];
            add(l, -1);
        }
        
        for (int i = 1; i <= n; ++i) printf("%d ", res[i]);
        cout << endl;
    }
    return 0;
}

Vijos P1448校門外的樹
校門外有不少樹,有蘋果樹,香蕉樹,有會扔石頭的,有能夠吃掉補充體力的……現在學校決定在某個時刻在某一段種上一種樹,保證任一時刻不會出現兩段相同種類的樹,現有兩個操做:
K=1,讀入l、r表示在區間[l,r]中種上一種樹,每次操做種的樹的種類都不一樣
K=2,讀入l,r表示詢問l~r之間能見到多少種樹
(l,r>0)
校門長度n,詢問次數m<=50000

/* 左右括號的方法。在一個區間內種樹,至關於加一對括號。用樹狀數組維護從起始到這個位置的左右括號的數量。
區間內有左括號那麼必定有這一種類型的樹,只有離開了對應的右括號這種樹纔沒有了。
因此爲了統計區間[x,y]內的樹種類,只需把y左邊左括號的個數-(x-1)左邊右括號的個數便可。 */
#include <bits/stdc++.h>

using namespace std;

int const N = 5e4 + 10;
int c1[N], c2[N], n, m;  // c1[x]維護左括號的數目,c2[x]維護右括號的數目

int lowbit(int x) {
    return x & -x;
}

void add(int c[], int x, int y) {
    for (int i = x; i <= n; i += lowbit(i)) c[i] += y;
}

int query(int c[], int x) {
    int res = 0;
    for (int i = x; i; i -= lowbit(i)) res += c[i];
    return res;
}

int main() {
    cin >> n >> m;
    for (int i = 1, op, l, r; i <= m; ++i) {
        scanf("%d%d%d", &op, &l, &r);
        if (op == 1) add(c1, l, 1), add(c2, r, 1);  // l處左括號數目加一,r處右括號數目加一
        else printf("%d\n", query(c1, r) - query(c2, l - 1));  // r的左括號數目-(l-1)的右括號數目
    }
    return 0;
}

POJ2155 Matrix
一個n*n的只含01二維矩陣,有m個操做,每一個操做兩種類型:
C x1 y1 x2 y2:把以(x1, y1)爲左上角,(x2, y2)爲右下角的矩陣的內的數所有反轉(0變1,1變0)
Q x y:查詢a[x][y]的數是多少
n、m~1e5

// 翻轉一個區間至關於區間的每一個數加1,最後是0仍是1就模2便可。
// 加1在模2的意義下就是把01反轉
#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
int const N = 1e3 + 10;
LL c[N][N], n, m, op, T;  // c[x][y]維護b[i][j]的前綴和

int lowbit(int x) {
    return x & (-x);
}

void add(int x, int y, LL z) {
    for (int i = x; i <= n; i += lowbit(i)) 
        for (int j = y; j <= n; j += lowbit(j))
            c[i][j] += z;
}

LL query(int x, int y) {
    LL res = 0;
    for (int i = x; i; i -= lowbit(i))
        for (int j = y; j; j -= lowbit(j))
            res += c[i][j];
    return res;
}

int main() {
    cin >> T;
    while (T--) {
        memset(c, 0, sizeof c);
        cin >> n >> m;
        for (int i = 1, x1, y1, x2, y2; i <= m; ++i) {
            char op[2];
            scanf("%s", op);
            if (op[0] == 'C') {  // 區間增長1
                scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
                add(x2 + 1, y2 + 1, 1);
                add(x1, y1, 1);
                add(x2 + 1, y1, -1);
                add(x1, y2 + 1, -1);
            }
            else {  // 單點查詢
                scanf("%d%d", &x1, &y1);
                printf("%lld\n", query(x1, y1) % 2);
            }
        }
        printf("\n");
    }
    return 0;
}

3.3 樹狀數組6大基本模板+推公式變形

ACM-ICPC 2018 徐州賽區網絡預賽 H.Ryuji doesn't want to study
一開始給定n個數,m次詢問。每次詢問兩種類型:
1 x y, 表示詢問a[l]*len + a[l + 1] * (len - 1) + ... + a[r] * 1,len = r - l + 1。
2 x y, 表示把a[x]賦值爲y
n ~ 1e5, m ~ 1e5
題解:\(\sum_{i=l}^ra[i]*(r - i + 1) = (r+1)\sum_{i=l}^ra[i] - \sum_{i=l}^ri * a[i]\)
所以,能夠使用樹狀數組分別維護a[i]和i * a[i]的前綴和便可

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;

int n, m;
int const N = 1e5 + 10;
LL c1[N], c2[N], a[N], sum1[N], sum2[N];  // c1[x]維護ai的前綴和,c2[x]維護i*ai的前綴和

LL lowbit(LL x) {
    return x & -x;
}

// 單點修改
void add(LL c[], int x, LL y) {
    for (int i = x; i <= n; i += lowbit(i)) c[i] += y;  // 不斷往父節點跳
}

// 查詢Sx前綴和
LL query(int x, LL c[]) {
    LL res = 0;
    for (int i = x; i; i -= lowbit(i)) res += c[i];  // 不斷往前一個兄弟節點跳
    return res;
}

int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; ++i) {
        scanf("%lld", &a[i]);  // 讀入a[i]
        sum1[i] = sum1[i - 1] + a[i];
        sum2[i] = sum2[i - 1] + i * a[i];
    }
    for (int i = 1; i <= n; ++i) {
        c1[i] = sum1[i] - sum1[i - lowbit(i)];
        c2[i] = sum2[i] - sum2[i - lowbit(i)];
    }
    while (m--) {
        LL op, x, y;
        scanf("%lld%lld%lld", &op, &x, &y);
        if (op == 1) {  // 詢問
            printf("%lld\n", (y + 1) * (query(y, c1) - query(x - 1, c1)) - (query(y, c2) - query(x - 1, c2)));
        }
        else {  // 單點修改
            add(c1, x, y - a[x]), add(c2, x, x * y - x * a[x]);
            a[x] = y;
        }
    }
    return 0;
}
相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息