1. 程式人生 > 實用技巧 >一般線段樹與權值線段樹

一般線段樹與權值線段樹

目錄

一般線段樹與權值線段樹

1.演算法分析

  1. 一般還要開4N的陣列
  2. 一般做單點修改、區間查詢,加上懶標記後,可以做區間修改、區間查詢

1.1 一般線段樹

可以處理:區間加、區間乘、區間max/min、區間覆蓋等問題

1.2 權值線段樹

  1. 維護全域性的值域資訊,每個節點記錄的是該值域的值出現的總次數。
  2. 使用二分的思想(離散化的時候,需要用到)
  3. 支援查詢全域性K小值,全域性rank,前驅,後繼等。
  4. 單詞操作時間複雜度為O(logn)
  5. 空間複雜度為O(n)
  6. 相對於平衡樹的優勢:程式碼簡單,速度快
  7. 劣勢:值域較大時,我們需要離散化,變成離線資料結構

2.板子

2.1 線段樹入門

2.1.1 單點修改+區間查詢

// 該板子是求區間和
#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
int const N = 5e5 + 10;
LL dat[N << 2];  // 4倍空間
int n, m, a[N];

// 上傳操作
void pushup(int rt) { 
    dat[rt] = dat[rt << 1] + dat[rt << 1 | 1];
}

// 建樹
void build (int rt, int l, int r) {
    if (l == r) {  // 如果當前到達葉節點,那麼賦值
        dat[rt] = a[l];  // 賦值是a[l],表示那個葉節點
        return ;
    }
    int mid = (l + r) >> 1;  
    // 遞迴建立左右子樹
    build(rt << 1, l, mid);  
    build(rt << 1 | 1, mid + 1, r);
    // 上傳
    pushup(rt);
}

// 單點修改
void modify (int rt, int l, int r, int x, int y) {
    if (l == x && r == x) {  // 遞迴到葉節點且葉節點剛好為x節點
        dat[rt] += y;  // 修改
        return;
    }
    int mid = (l + r) >> 1;  
    if (x <= mid) modify(rt << 1, l, mid, x, y);  // 如果在左子樹 
    else modify(rt << 1 | 1, mid + 1, r, x, y);  // 不在左子樹,比在右子樹
    pushup(rt);  // 上傳
}

// 區間查詢
LL query(int rt, int l, int r, int L, int R) {
    if (L <= l && r <= R) return dat[rt];  // 如果當前rt管轄的點能夠被[L, R]完全包含,返回
    int mid = (l + r) >> 1;
    LL res = 0;
    if (L <= mid) res += query(rt << 1, l, mid, L, R);  // 如果和左子樹有關
    if (mid < R) res += query(rt << 1 | 1, mid + 1, r, L, R);  // 如果可能和右子樹有關
    return res;
}

int main() {
    cin >> n >> m;
    for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
    build(1, 1, n);  // 建樹
    for (int i = 1, op, x, y; i <= m; ++i) {
        scanf("%d%d%d", &op, &x, &y);  
        if (op == 1) modify(1, 1, n, x, y);  // 單點修改a[x]+=y
        else printf("%lld\n", query(1, 1, n, x, y));  // 區間查詢,求[x, y]的區間和
    }
    return 0;
}

2.1.2 區間修改+區間查詢

// 該板子是求區間和
#include <bits/stdc++.h>

using namespace std;

typedef long long LL;

int const N = 5e5 + 10;
LL dat[N << 2], lazy[N << 2];
int a[N], n, m;

// 上傳標記,每次左右子樹建樹/區間修改完都需要上傳
void pushup(int rt) {
    dat[rt] = dat[rt << 1] + dat[rt << 1 | 1]; 
}

// 建樹
void build(int rt, int l, int r) {
    if (l == r) {  // 遞迴到葉節點
        dat[rt] = a[l];
        lazy[rt] = 0;
        return;
    }
    // 遞迴建立左右子樹
    int mid = (l + r) >> 1;
    build(rt << 1, l, mid);  
    build(rt << 1 | 1, mid + 1, r);
    pushup(rt);  // 上傳
}

// 下傳,下傳標記,同時改變dat陣列
void pushdown(int rt, int l, int r) {
    if (lazy[rt]) {  // 如果有標記
        int mid = (l + r) >> 1;
        
        // 把標記給左右子樹
        lazy[rt << 1] += lazy[rt];  
        lazy[rt << 1 | 1] += lazy[rt];
        
        // 改變dat
        dat[rt << 1] += (mid - l + 1) * lazy[rt];
        dat[rt << 1 | 1] += (r - mid) * lazy[rt];
        
        // rt標記清空
        lazy[rt] = 0;
    }
    return;
}

// 區間修改: [L, R] += x
void modify(int rt, int l, int r, int L, int R, int x) {
    if (L <= l && r <= R) {  // 如果當前區間被完全包含
        dat[rt] += (r - l + 1) * x;  // 修改當前區間的dat值
        lazy[rt] += x;  // 改變懶標記
        return ;
    }
    
    pushdown(rt, l, r);  // 下傳
    // 遞迴左右子樹修改區間
    int mid = (l + r) >> 1;
    if (L <= mid) modify(rt << 1, l, mid, L, R, x);
    if (mid < R) modify(rt << 1 | 1, mid + 1, r, L, R, x);
    pushup(rt);  // 上傳
    return;
}

// 區間查詢:獲得[L, R]的區間和
LL query(int rt, int l, int r, int L, int R) {
    if (L <= l && r <= R) return dat[rt];  // 如果[l, r]被完全包含於[L, R]
    pushdown(rt, l, r);  // 標記下傳
    // 遞迴加上左右子樹
    int mid = (l + r) >> 1;
    LL res = 0;
    if (L <= mid) res += query(rt << 1, l, mid, L, R);
    if (mid < R) res += query(rt << 1 | 1, mid + 1, r, L, R);
    return res;
}

int main() {
    cin >> n >> m;
    for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);  // 讀入陣列
    build(1, 1, n);  // 建樹
    for (int i = 1, a, b, x, op; i <= m; ++i) {
        scanf("%d", &op);
        if (op == 1) {
            scanf("%d%d%d", &a, &b, &x);
            modify(1, 1, n, a, b, x);  // 區間修改, [a, b] += x
        }
        else {
            scanf("%d%d", &a, &b);
            printf("%lld\n", query(1, 1, n, a, b));  // 區間查詢,查詢[a, b]的區間和
        }
    }
    return 0;
}

2.1.3 區間加乘操作

// 加乘模板
// x點原來的乘、加法標記為:mul1、add1,後來要加上的乘、加法標記為:mul2、add2
// 可以證明先乘後加最優方法
// x的值變為: x.dat => (x.dat * mul2) + (x.r - x.l + 1) * add2;
// x的乘法標記變為: x.mul1 => x.mul1 * mul2
// x的加法標記變為: x.add1 => x.add1 * mul2 + add2
#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
int const N = 1e5 + 10;
LL dat[N << 2], mul[N << 2], add[N << 2];
int n, p, a[N], m;

// 上傳,根的值為左子樹的值和右子樹的值之和
void pushup(int rt) {
    dat[rt] = (dat[rt << 1] + dat[rt << 1 | 1]) % p;
}

// 建樹
void build(int rt, int l, int r) {
    if (l == r) {  // 如果是葉子
        dat[rt] = a[l] % p;
        add[rt] = 0;
        mul[rt] = 1;
        return;
    }
    
    // 如果不是葉子,那麼乘法標記必須為1,加法標記為0
    mul[rt] = 1;
    add[rt] = 0;
    int mid = (l + r) >> 1;
    build(rt << 1, l, mid), build(rt << 1 | 1, mid + 1, r);  // 建立左子樹和右子樹
    pushup(rt);
}

// 加成的結果
void eval(int rt, int l, int r, LL add2, LL mul2) {
    dat[rt] = ((dat[rt] * mul2 % p) + ((r - l + 1) % p) * add2 % p) % p;
    mul[rt] = mul[rt] * mul2 % p;
    add[rt] = (add[rt] * mul2 % p + add2) % p;
}

// 標記下移
void pushdown(int rt, int l, int r) {
    int mid = (l + r) >> 1;
    eval(rt << 1, l, mid, add[rt], mul[rt]), eval(rt << 1 | 1, mid + 1, r, add[rt], mul[rt]);  // 左右子樹分別得到根的標記
    add[rt] = 0, mul[rt] = 1;  // 清空根的標記
    return;
}

// 區間修改
void modify(int rt, int l, int r, int L, int R, LL add2, LL mul2) {
    if (L <= l && r <= R) {  // 如果[L, R]在[l, r]內,直接修改
        eval(rt, l, r, add2, mul2);
        return;
    }
    pushdown(rt, l, r);  // 如果不在[l, r]內,那麼分裂,首先要把標記下移
    int mid = (l + r) >> 1;
    if (L <= mid) modify(rt << 1, l, mid, L, R, add2, mul2);  // 修改左子樹
    if (mid < R) modify(rt << 1 | 1, mid + 1, r, L, R, add2, mul2);  // 修改右子樹
    pushup(rt);  // 修改完子樹需要把標記上移
    return;
}

// 詢問區間和[L, R]
LL query(int rt, int l, int r, int L, int R) {
    if (L <= l && r <= R) return dat[rt] % p;
    pushdown(rt, l, r);  // 如果[L, R]不在[l, r]內,那麼需要分裂,首先要把標記下移
    LL res = 0;
    int mid = (l + r) >> 1;
    if (L <= mid) res = query(rt << 1, l, mid, L, R) % p;  // 左子樹
    if (mid < R) res = (res + query(rt << 1 | 1, mid + 1, r, L, R) % p) % p;  // 右子樹
    return res;
}

int main() {
    cin >> n >> p;  // 輸入數字的個數和模數
    for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);  // 輸入數字
    build(1, 1, n);  // 建樹
    cin >> m;  // 輸入運算元
    for (int i = 1, op, t, g, c; i <= m; ++i) {  // 輸入每次的具體操作
        scanf("%d", &op);
        if (op == 1) {  // 區間乘
            scanf("%d%d%d", &t, &g, &c);
            modify(1, 1, n, t, g, 0, c);
        }
        else if (op == 2) {  // 區間加
            scanf("%d%d%d", &t, &g, &c);
            modify(1, 1, n, t, g, c, 1);
        }
        else {  // 詢問區間和
            scanf("%d%d", &t, &g);
            cout << query(1, 1, n, t, g) << endl;
        }
    }
    return 0;
}

2.1.4 區間染色

/*本題是區間覆蓋問題,求指定區間內有多少的顏色數目,因為顏色的數目比較少,因此
可以使用一個int整數來表示所有的顏色數目,而後就是線段樹的常規操作*/
#include <bits/stdc++.h>

using namespace std;

int const N = 1e5 + 10;
typedef long long LL;

LL add[N << 2], sum[N << 2];  // add為記錄顏色的懶標記,sum為當前區間的顏色

// 向下傳遞操作
void pushup(int u) {
    sum[u] = sum[u << 1] | sum[u << 1 | 1];  // 當前顏色由子區間顏色得到
}

// 向上傳遞操作
void pushdown(int u) {
    if (add[u]) {  // 如果當前u節點有顏色的話

        // 給左右子節點標記都賦值
        add[u << 1] = add[u];  
        add[u << 1 | 1] = add[u];

        // 給左右節點的sum賦值,記錄顏色
        sum[u << 1] = add[u];
        sum[u << 1 | 1] = add[u];

        // 去掉懶標記
        add[u] = 0;
    }
}

// 建樹
void build(int u, int l, int r) {
    add[u] = 0;  // 初始每個節點都沒有懶標記
    if (l == r) {  // 如果遞迴到葉節點
        sum[u] = 1;  // 葉節點的顏色賦值
        return;
    }
    int mid = l + r >> 1;
    build(u << 1, l, mid);  // 建立左右子樹
    build(u << 1| 1, mid + 1, r);
    pushup(u);  // 標記上傳
}

// 區間賦值操作
void modify(int u, int l, int r, int c, int L, int R) {
    if (L <= l && r <= R) {  // 如果[l, r]完全被包含在要賦值的區間[L, R]的話,那麼直接修改
        add[u] = 1 << (c - 1);
        sum[u] = 1 << (c - 1);
        return;
    }
    pushdown(u);  // 下傳標記,因為因為標記要分裂
    int mid = l + r >> 1;
    if (L <= mid) modify(u << 1, l, mid, c, L, R);  // 遞迴修改左右子樹
    if (mid < R) modify(u << 1 | 1, mid + 1, r, c, L, R);
    pushup(u);  // 上傳操作
}

// 區間查詢多少個顏色
LL query(int u, int l, int r, int L, int R) {
    if (L <= l && r <= R) return sum[u];  // 如果[l, r]完全被包含在要賦值的區間[L, R]的話,那麼返回
    pushdown(u);  // 標記下移
    int mid = l + r >> 1;
    LL res = 0;

    // 遞迴查詢左右子樹
    if (L <= mid) res |= query(u << 1, l, mid, L, R);
    if (mid < R) res |= query(u << 1 | 1, mid + 1, r, L, R);
    return res;
}

int main() {
    int L, T, O, a, b, c; 
    cin >> L >> T >> O;  // 讀入節點數、顏色總數、運算元
    build(1, 1, L);
    while (O--) {
        char op[2];
        scanf("%s", op);  // 讀入操作型別
        if (op[0] == 'P') {
            scanf("%d %d", &a, &b);
            if (a > b) swap(a, b);  // 保證a要比b小
            LL ans = query(1, 1, L, a, b);  // 查詢a到b的顏色總數,顏色總數用一個int型數表示
            LL res = 0;  
            while (ans) {  // 記錄這個int型數有多少個1
                if (ans & 1) res++;
                ans >>= 1;
            }
            printf("%lld\n", res);
        }
        else {
            scanf("%d%d%d", &a, &b, &c);  // 讀入[a, b]和修改為的值
            if (a > b) swap(a, b);
            modify(1, 1, L, c, a, b);  // 修改操作
        }
    }
    return 0;
}

2.2 權值線段樹

2.2.1 求第k大、前驅、後繼等

/*
本題由於一開始dat維護的全為0,所以不需要建樹的操作。dat維護每個數出現的次數
資料較大,需要先離散化,然後在每個離散化後的數字上建立線段樹維護每個數出現的次數。
1. 插入數值x:x的次數加一
2. 刪除數值x(若有多個相同的數,應只刪除一個):x的次數減一
3. 查詢數值x的排名(若有多個相同的數,應輸出最小的排名):區間查詢[l, x - 1]的次數,然後加一
4. 查詢排名為x的數值:看x是否小於等於左子樹的次數,如果小於在左子樹;否則就算右子樹的k-左子樹次數
5. 求數值x的前驅(前驅定義為小於x的最大的數):求出x的排名t,然後查詢排名為t-1的數
6. 求數值x的後繼(後繼定義為大於x的最小的數):求出x的排名t,然後查詢排名為t+1的數
*/
#include <bits/stdc++.h>

using namespace std;

const int N=100005;

int num[N];
struct A{
    int opt, x;
}q[N];
int dat[N << 2];
 
void pushup(int rt){
    dat[rt] = dat[rt << 1] + dat[rt << 1 | 1];
}
 
// 單點修改
void modify(int rt, int l, int r, int p, int c){
    if(l == r){
        dat[rt] += c;
        return;
    }
    int mid = (l + r) >> 1;
    if(p <= mid) modify(rt << 1, l, mid, p, c);
    else modify(rt << 1 | 1, mid + 1, r, p, c);
    pushup(rt);
}
 
// 區間查詢
int query1(int rt, int l, int r, int L, int R){//區間求和
    if (L <= l && r <= R) return dat[rt];
    int mid = (l + r) >> 1;
    int res = 0;
    if(L <= mid) res += query1(rt << 1, l, mid, L, R);
    if (mid < R) res += query1(rt << 1 | 1, mid + 1, r, L, R);
    return res;
}

// 查詢排名為k的數
int query2(int rt, int l, int r, int k)
{
    if(l == r) return l;
    int mid = (l + r) >> 1;
    if(k <= dat[rt << 1]) return query2(rt << 1, l, mid, k);
    else return query2(rt << 1 | 1, mid + 1, r, k-dat[rt<<1]);
}
 
int main(){
    int m, k=0;
    scanf("%d", &m);
    for(int i = 0; i < m; i++){
        scanf("%d%d", &q[i].opt, &q[i].x);
        if(q[i].opt != 4) num[k++] = q[i].x;
    }
    sort(num, num+k);
    int n = unique(num, num+k) - num;
 
    for(int i = 0; i < m; i++){
        int x = lower_bound(num, num+n, q[i].x) - num + 1;
        if(q[i].opt == 1){//插入
            modify(1, 1, n, x, 1);
        }
        if(q[i].opt == 2){//刪除
            modify(1, 1, n, x, -1);
        }
        if(q[i].opt == 3){//查詢x的排名
            if(x - 1 == 0) printf("1\n");
            else printf("%d\n", query1(1, 1, n, 1, x - 1) + 1);
        }
        if(q[i].opt == 4){//查詢排名為x的數
            printf("%d\n", num[query2(1, 1, n, q[i].x) - 1]);
        }
        if(q[i].opt == 5){//求小於x的最大的數的值
            int rk = query1(1, 1, n, 1, x - 1);
            printf("%d\n", num[query2(1, 1, n, rk) - 1]);
        }
        if(q[i].opt == 6){//求大於x的最小的數的值
            int sum = query1(1, 1, n, 1, x);
            printf("%d\n", num[query2(1, 1, n, sum + 1) - 1]);
        }
    }
    return 0;
}

3. 例題

3.1 線段樹入門

luogu P1047 校門外的樹
題意: 有一個數軸,長度為l+1,從0~l上每個點都種樹。現在有m個操作,每個操作輸入a和b,表示要把[a, b]上的樹砍掉,問m次操作後,數軸上還剩下多少棵樹?
題解: 只需要改區間修改+區間查詢的板子即可,當砍掉[a,b]上的樹時,就算把[a, b]賦值為0,最後統計還剩多少棵樹,就算計算[1,n]的區間求和。
程式碼:

// 該板子是求區間和
#include <bits/stdc++.h>

using namespace std;

typedef long long LL;

int const N = 5e5 + 10;
LL dat[N << 2], lazy[N << 2];
int a[N], n, m;

// 上傳標記,每次左右子樹建樹/區間修改完都需要上傳
void pushup(int rt) {
    dat[rt] = dat[rt << 1] + dat[rt << 1 | 1]; 
}

// 建樹
void build(int rt, int l, int r) {
    if (l == r) {  // 遞迴到葉節點
        dat[rt] = a[l];
        lazy[rt] = 1;
        return;
    }
    lazy[rt] = 1;
    // 遞迴建立左右子樹
    int mid = (l + r) >> 1;
    build(rt << 1, l, mid);  
    build(rt << 1 | 1, mid + 1, r);
    pushup(rt);  // 上傳
}

// 下傳,下傳標記,同時改變dat陣列
void pushdown(int rt, int l, int r) {
    if (lazy[rt] == 0) {  // 如果有標記
        
        // 把標記給左右子樹
        lazy[rt << 1] = 0;  
        lazy[rt << 1 | 1] = 0;
        
        // 改變dat
        dat[rt << 1] = 0 ;
        dat[rt << 1 | 1] = 0;
        
        // rt標記清空
        lazy[rt] = 1;
    }
    return;
}

// 區間修改: [L, R] += x
void modify(int rt, int l, int r, int L, int R) {
    if (L <= l && r <= R) {  // 如果當前區間被完全包含
        dat[rt] = 0;  // 修改當前區間的dat值
        lazy[rt] = 0;  // 改變懶標記
        return ;
    }
    
    if (lazy[rt] == 0) pushdown(rt, l, r);  // 下傳
    // 遞迴左右子樹修改區間
    int mid = (l + r) >> 1;
    if (L <= mid) modify(rt << 1, l, mid, L, R);
    if (mid < R) modify(rt << 1 | 1, mid + 1, r, L, R);
    pushup(rt);  // 上傳
    return;
}

// 區間查詢:獲得[L, R]的區間和
LL query(int rt, int l, int r, int L, int R) {
    if (L <= l && r <= R) return dat[rt];  // 如果[l, r]被完全包含於[L, R]
    if (lazy[rt] == 0) pushdown(rt, l, r);  // 標記下傳
    // 遞迴加上左右子樹
    int mid = (l + r) >> 1;
    LL res = 0;
    if (L <= mid) res += query(rt << 1, l, mid, L, R);
    if (mid < R) res += query(rt << 1 | 1, mid + 1, r, L, R);
    return res;
}

int main() {
    cin >> n >> m;
    n ++;
    for (int i = 1; i <= n; ++i) a[i] = 1;
    build(1, 1, n);  // 建樹
    // cout << query(1, 1, n, 1, n) << endl;
    for (int i = 1, a, b; i <= m; ++i) {
        scanf("%d%d", &a, &b);
        a++, b++;
        modify(1, 1, n, a, b);
    }
    cout << query(1, 1, n, 1, n) << endl;
    return 0;
}

luogu P5057 [CQOI2006]簡單題
題意: 有一個 n 個元素的陣列,每個元素初始均為 0。有 m 條指令,要麼讓其中一段連續序列數字反轉——0 變 1,1 變 0(操作 1),要麼詢問某個元素的值(操作 2)。 1 ≤ n ≤ 10^5^, 1 ≤ m ≤ 5 × 10^5^
題解: 線段樹維護,每次給定反轉區間[a, b],那麼把[a, b]區間中每個數字加1,而後每次詢問x的時候,只需要query(1,1,n,x,x),而後判斷這個值是奇數還是偶數,奇數輸出1,偶數輸出0即可
程式碼:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;

int const N = 1e5 + 10;
LL dat[N << 2], lazy[N << 2];
int n, m;

// 上傳標記,每次左右子樹建樹/區間修改完都需要上傳
void pushup(int rt) {
    dat[rt] = dat[rt << 1] + dat[rt << 1 | 1]; 
}

// 建樹
void build(int rt, int l, int r) {
    if (l == r) {  // 遞迴到葉節點
        dat[rt] = 0;
        lazy[rt] = 0;
        return;
    }
    // 遞迴建立左右子樹
    int mid = (l + r) >> 1;
    build(rt << 1, l, mid);  
    build(rt << 1 | 1, mid + 1, r);
    pushup(rt);  // 上傳
}

// 下傳,下傳標記,同時改變dat陣列
void pushdown(int rt, int l, int r) {
    if (lazy[rt]) {  // 如果有標記
        int mid = (l + r) >> 1;
        
        // 把標記給左右子樹
        lazy[rt << 1] += lazy[rt];  
        lazy[rt << 1 | 1] += lazy[rt];
        
        // 改變dat
        dat[rt << 1] += (mid - l + 1) * lazy[rt];
        dat[rt << 1 | 1] += (r - mid) * lazy[rt];
        
        // rt標記清空
        lazy[rt] = 0;
    }
    return;
}

// 區間修改: [L, R] += x
void modify(int rt, int l, int r, int L, int R, int x) {
    if (L <= l && r <= R) {  // 如果當前區間被完全包含
        dat[rt] += (r - l + 1) * x;  // 修改當前區間的dat值
        lazy[rt] += x;  // 改變懶標記
        return ;
    }
    
    pushdown(rt, l, r);  // 下傳
    // 遞迴左右子樹修改區間
    int mid = (l + r) >> 1;
    if (L <= mid) modify(rt << 1, l, mid, L, R, x);
    if (mid < R) modify(rt << 1 | 1, mid + 1, r, L, R, x);
    pushup(rt);  // 上傳
    return;
}

// 區間查詢:獲得[L, R]的區間和
LL query(int rt, int l, int r, int L, int R) {
    if (L <= l && r <= R) return dat[rt];  // 如果[l, r]被完全包含於[L, R]
    pushdown(rt, l, r);  // 標記下傳
    // 遞迴加上左右子樹
    int mid = (l + r) >> 1;
    LL res = 0;
    if (L <= mid) res += query(rt << 1, l, mid, L, R);
    if (mid < R) res += query(rt << 1 | 1, mid + 1, r, L, R);
    return res;
}

int main() {
    cin >> n >> m;
    build(1, 1, n);  // 建樹
    for (int i = 1, a, b, x, op; i <= m; ++i) {
        scanf("%d", &op);
        if (op == 1) {
            scanf("%d%d", &a, &b);
            modify(1, 1, n, a, b, 1);  // 區間修改, [a, b] += 1
        }
        else {
            scanf("%d", &a);
            printf("%lld\n", (query(1, 1, n, a, a) & 1) == 1);  // 區間查詢,查詢[a, b]的區間和
        }
    }
    return 0;
}

luogu P4588 [TJOI2018]數學計算
題意: 小豆現在有一個數x,初始值為1.小豆有Q次操作,操作有兩種型別:
1 m: x = x * m, 輸出x%mod;
2 pos:x= x = x / 第pos次操作所乘的數(保證第pos次操作一定為型別1,對於每一個型別1的操作至多會被除一次)輸出x % mod;Q <= 10^5^
題解: 使用線段樹維護1~Q這Q個數字的區間乘,如果當前是1型別操作,那麼進行單點修改modify(1, 1, n, i, x);如果是2型別操作,那麼進行單點修改modify(1, 1, n, pos, 1); 每次輸出都是所有的成績, 即dat[1];
程式碼:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
int const N = 1e5 + 10;
LL dat[N << 2];
int n, p, t;

void pushup(int rt) {
    dat[rt] = dat[rt << 1] * dat[rt << 1 | 1] % p;
}

void build(int rt, int l, int r) {
    if (l == r) {
        dat[rt] = 1;
        return;
    }
    int mid = (l + r) >> 1;
    build(rt << 1, l, mid), build(rt << 1 | 1, mid + 1, r);
    pushup(rt);
}

void modify(int rt, int l, int r, int x, int y) {
    if (l == r && l == x) {
        dat[rt] = y;
        return;
    }
    int mid = (l + r) >> 1;
    if (x <= mid) modify(rt << 1, l, mid, x, y);
    else modify(rt << 1 | 1, mid + 1, r, x, y);
    pushup(rt);
}

int main() {
    cin >> t;
    while (t--) {
        cin >> n >> p;
        for (int i = 1; i <= n * 4; ++i) dat[i] = 0;
        build(1, 1, n);
        for (int i = 1, op, x; i <= n; ++i) {
            scanf("%d%d", &op, &x);
            if (op == 1) modify(1, 1, n, i, x % p);
            else modify(1, 1, n, x, 1);
            printf("%lld\n", dat[1] % p);
        }
    }
    return 0;
}

3.2 權值線段樹

luogu P1908 逆序對
題意: 求出一個數列的逆序對.數列長度n ≤ 5×10^5^
題解: 權值線段樹維護每個數字出現的次數,然後每個數字x出現的時候只需要區間查詢[1, x - 1]的出現次數即可
程式碼:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
int const N = 500050;

int n;
LL a[N], b[N];
int dat[N << 2];

LL ans = 0;

void modify(int rt, int l, int r, int x)
{
	if(l == r)
	{
		dat[rt]++;
		return;
	}
	int mid = (l + r) >> 1;
	if(x <= mid) modify(rt << 1, l, mid, x);
	else modify(rt << 1 | 1, mid + 1, r, x);
	dat[rt] = dat[rt << 1] + dat[rt << 1 | 1];
}

int query(int rt,int l,int r,int L,int R)
{
	if(L <= l && r <= R) return dat[rt];
	int mid = (l + r) >> 1;
	int res = 0;
	if(L <= mid) res += query(rt << 1, l, mid, L, R);
	if(mid < R) res += query(rt << 1 | 1, mid + 1, r, L, R);
	return res;
}

int main()
{
	cin >> n;
	for(int i = 1; i <= n; i++)
	{
	    cin >> a[i];
	    b[i] = a[i];
	}
	sort(b + 1, b + 1 + n);
	int len = unique(b + 1, b + n + 1) - b - 1;
	for(int i = 1; i <= n; i++)
	{
		int pos = lower_bound(b + 1, b + n + 1, a[i]) - b;
		a[i] = pos;
	}
	for(int i = 1; i <= n; i++)
	{
		int x = a[i];
		ans += query(1, 1, n, x + 1, n);
		modify(1, 1, n, x);
	} 
	printf("%lld", ans);
	
	return 0;
}