主席樹學習筆記

今天學習了一個特殊的線段樹結構,主席樹。

概念:

主席樹是一種可持久化的線段樹結構,然後沒了= =

用途:

主席樹的最基本用途是查詢a[l] ……a[r]x[L,R]x 的個數。
引申還可以查詢區間中第K小的數是什麼。等等……

結構:

對於主席樹的結構理解,我自己閱讀多篇他人的博客,才終於明白了是怎麼一回事。不知是博客寫的不好還是我的理解能力太差= =大概是後者吧。
那我就按照自己的理解來講清楚這究竟是什麼樣的結構。

初步結構要點:
1、主席樹可以理解爲線段樹的集合。也就是說,很多棵線段樹合起來叫主席樹(先這麼理解好了)
2、對於每棵線段樹,它保存的是大小屬於[L, R]的數的個數(先這麼理解好了)
3、線段樹之間的區別就是線段樹保存的數據範圍a[i]不同, 記線段樹T[i]表示對於原序列a[1]……a[i]前綴中,大小屬於[L, R]的數的個數。(不曉得自己有沒有講清楚)
初步結構分析:
1、線段樹的範圍應該是1……maxN(maxN表示序列中的最大值)
2、一棵線段樹的理論空間大小是O(2maxN),於是主席樹的理論空間大小是O(N*maxN),N是序列長度。(對於每個前綴序列,都有一棵線段樹,一共N個前綴)
3、對於任意的兩棵線段樹,樹中對應的節點表示的含義相同,即[l, r]的數的個數;只是數據範圍不同,它們對應着兩個包含與被包含的前綴。所以T[i]的範圍小於T[j](i < j)。因爲對應的節點含義相同,所以對應的節點可以相加減。T[j] - T[i - 1]應該表示着a[i]……a[j]範圍中,屬於[L, R]的數的個數(i < j)。
進階分析I:

鑑於a[i]的大小可能會非常大(eg. a[i] <= 109 ), 我們可以先將原序列離散化成N個數據點,再將N個數據點做成線段樹。這樣線段樹的空間爲O(N) , 因此主席樹的空間爲O(N2)

進階分析II:

分析T[i]與T[i + 1]。它們實際相差的範圍只是後者比前者在數據範圍中多了一個a[i + 1]。
分析T[i]到T[i + 1]的動態變化,其實只是從線段樹的根節點到某個葉子節點這麼一條路徑發生了變化(保存的值都加了一)。於是利用指針,T[i + 1]的大部分節點都可以沿用T[i],只是少量節點需要重新開闢(變化的節點)。
可以想象,這樣就把一棵棵獨立的線段樹聯繫成了一個整體 -> 主席樹。至於主席樹究竟長成什麼樣,有點複雜,但是有一點是可以明確的,兩棵相鄰的線段樹關係是非常清楚的。
由於大量地沿用了之前地節點,主席樹的空間複雜度大大降低變成O(NlogN) ,變成了可以承受的範圍了。

總結:

至此,一棵主席樹就搭建完成了。關於時間複雜度,其實也就是兩次線段樹的複雜度,因此時間複雜度是O(NlogN)


代碼:

const int maxn = 1e5 + 5;

int a[maxn], a2[maxn]; //a爲原序列,a2爲排序離散化序列

struct Node {
    Node *lch, *rch;
    int sz;
    Node() {lch = rch = NULL, sz = 0;}
    Node (Node *l, Node *r, int _sz) : sz(_sz) {lch = l, rch = r;}
    void update() {
        if (lch != NULL) sz += lch ->sz;
        if (rch != NULL) sz += rch ->sz;
    }
};
Node *tp = new Node();
Node *root[maxn] = {NULL};

void build(Node *&x, Node *&y, int val, int l, int r) {
    if (x == NULL) x = tp;
    y = new Node();
    int m = (l + r) >> 1;
    if (l == r) {
        *y = *x;
        y ->sz ++;
        return;
    }
    if (val <= a2[m]) {
        build(x ->lch, y ->lch, val, l, m);
        y ->rch = x ->rch;
        y ->update();
    }
    else {
        build(x ->rch, y ->rch, val, m + 1, r);
        y ->lch = x ->lch;
        y ->update();
    }
}

int query(Node *&L, Node *&R, int l, int r, int k) {
    if (L == NULL) L = tp;
    if (R == NULL) R = tp;
    if (l == r) return a2[l];
    int m = (l + r) >> 1;
    int ans = 0;
    if (R ->lch) ans += R ->lch ->sz;
    if (L ->lch) ans -= R ->lch ->sz;

    if (ans >= k) return query(L ->lch, R ->lch, l, m, k);
    else return query(L ->rch, R ->rch, m + 1, r, k - ans);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章