淺析主席樹

由於作者太蒟了,不會動態主席樹,所以這裏就只講靜態主席樹。其實會了平衡樹什麼問題都沒了。

一.定義

可持久化線段樹它就牛逼在一個別名:主席數
什麼叫可持久化呢?我也無法解釋。其實主席樹就是用來解決區間第k小的值的這類問題。整個算法相當於用空間換時間(只要有空間,啥都好說)。

二.思想

這裏有道模板題:
Luogu P3834 【模板】可持久化線段樹 1(主席樹)
相信大家都會線段樹吧,但是用線段樹來解決區間第k小太耗時間了。於是,某人就想出了一個名爲主席樹的算法。
我們針對每一個點i都建一棵權值線段樹,什麼意思呢?就是說針對每一個數i重新建一個根,這個根的所有兒子節點的範圍包括它自己的範圍與上一個根的都一樣,但如果某個兒子節點的範圍包含了i,那麼就要重建一個這個兒子節點,其權值爲上一個根的對應節點的權值+1,這些節點要一直建到葉節點。
很抽象,對不對?下面結合圖來理解一下:
原樹:
在這裏插入圖片描述
然後開始插入第一個數:
在這裏插入圖片描述
看到沒,節點(1,3)沒有包括4所以就不需要再建下去,而(4,6)包括4,就讓其新建一個節點,且權值是原對應節點的全值加1。
再來一個:
在這裏插入圖片描述
這下看懂了吧。
那麼建了這棵樹有什麼用呢?
不知道大家有沒有看出來,這其實就是一個前綴和的思想,那麼怎麼搞呢?
你先看哈,如果我們要求區間2~4中第二小的數,就把我們第4次建的那棵樹的所有點的權值減去第1次建的樹的所有點的權值(要求2到4的和就sum[4]-sum[1])然後就可以得到一顆新樹,再在這棵樹上像原來的的線段樹一樣搞第k小的數就行了。是不是很簡單?
下面分步用代碼解釋一下:

三.實現

主席樹不需要建樹

1.預處理離散化

這裏由於數值可能很大,但是數的個數卻很少,所以我們要用unique來離散化:

    for (int i = 1; i <= n; i ++){
        scanf ("%d", &a[i]);
        b[i] = a[i];
    }
    sort (b + 1, b + 1 + n);
    len = unique (b + 1, b + 1 + n) - b - 1;//len就是離散化後的數列長度

2.插入

插入我們就先用low_bound找到數的位置,然後用update進行插入:

int update (int Index, int l, int r){
    int now = ++ siz;
    L[now] = L[Index], R[now] = R[Index], sum[now] = sum[Index] + 1;//前綴和累加
    if (l == r)//邊界條件
        return now;
    int mid = (l + r) / 2;
    if (mid >= p)
        L[now] = update (L[now], l, mid);
    else
        R[now] = update (R[now], mid + 1, r);
    return now;
}
    for (int i = 1; i <= n; i ++){
        p = lower_bound (b + 1, b + 1 + len, a[i]) - b;
        rt[i] = update (rt[i - 1], 1, len);
    }

3.查找

這裏就不用再多說了,Code:

int query (int rl, int rr, int l, int r, int k){
    int mid = (l + r) / 2, x = sum[L[rr]] - sum[L[rl]];//前綴和相減
    if (l == r)
        return l;
    if (x >= k)//如果左兒子所含的數大於等於k個,說明第k大在左兒子裏,就搜左兒子
        return query (L[rl], L[rr], l, mid, k);
    else
        return query (R[rl], R[rr], mid + 1, r, k - x);//注意要減去左兒子所含的數的個數
}
printf ("%d\n", b[query (rt[l - 1], rt[r], 1, len, k)]);

最後你就可以實現了。
注意:數組大小一般要開32倍!

四.模板

同上面的題:
Code

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

#define M 200005 * 32

int n, m, a[M], b[M], len, siz, rt[M], L[M], R[M], sum[M], p;

void build (int &Index, int l, int r){
    Index = ++ siz;
    sum[Index] = 0;
    if (l == r)
        return ;
    int mid = (l    + r) / 2;
    build (L[Index], l, mid);
    build (R[Index], mid + 1, r);
}
int update (int Index, int l, int r){
    int now = ++ siz;
    L[now] = L[Index], R[now] = R[Index], sum[now] = sum[Index] + 1;
    if (l == r)
        return now;
    int mid = (l + r) / 2;
    if (mid >= p)
        L[now] = update (L[now], l, mid);
    else
        R[now] = update (R[now], mid + 1, r);
    return now;
}
int query (int rl, int rr, int l, int r, int k){
    int mid = (l + r) / 2, x = sum[L[rr]] - sum[L[rl]];
    if (l == r)
        return l;
    if (x >= k)
        return query (L[rl], L[rr], l, mid, k);
    else
        return query (R[rl], R[rr], mid + 1, r, k - x);
}
int main (){
    scanf ("%d %d", &n, &m);
    for (int i = 1; i <= n; i ++){
        scanf ("%d", &a[i]);
        b[i] = a[i];
    }
    sort (b + 1, b + 1 + n);
    len = unique (b + 1, b + 1 + n) - b - 1;
    //build (rt[0], 1, len);
    for (int i = 1; i <= n; i ++){
        p = lower_bound (b + 1, b + 1 + len, a[i]) - b;
        rt[i] = update (rt[i - 1], 1, len);
    }
    while (m --){
        int l, r, k;
        scanf ("%d %d %d", &l, &r, &k);
        printf ("%d\n", b[query (rt[l - 1], rt[r], 1, len, k)]);
    }
    return 0;
}

Perfect!

五.再來一題

KUR-Couriers
一樣的道理,把第k小改成了出現k次。
Code

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

#define M 500005 * 32

int n, m, a[M / 32], b[M / 32], len, siz, rt[M], L[M], R[M], sum[M], p;

int update (int Index, int l, int r){
    int now = ++ siz;
    L[now] = L[Index], R[now] = R[Index], sum[now] = sum[Index] + 1;
    if (l == r)
        return now;
    int mid = (l + r) / 2;
    if (mid >= p)
        L[now] = update (L[now], l, mid);
    else
        R[now] = update (R[now], mid + 1, r);
    return now;
}
int query (int rl, int rr, int l, int r, int k){
    int mid = (l + r) / 2, x = sum[L[rr]] - sum[L[rl]], y = sum[R[rr]] - sum[R[rl]];
    if (l == r)
        return l;
    int ans = 0;
    if (x >= k)
        ans = query (L[rl], L[rr], l, mid, k);
    if (ans)
        return ans;
    if (y >= k)
        ans = query (R[rl], R[rr], mid + 1, r, k);
    return ans;
}
int main (){
    scanf ("%d %d", &n, &m);
    for (int i = 1; i <= n; i ++){
        scanf ("%d", &a[i]);
        b[i] = a[i];
    }
    sort (b + 1, b + 1 + n);
    len = unique (b + 1, b + 1 + n) - b - 1;
    for (int i = 1; i <= n; i ++){
        p = lower_bound (b + 1, b + 1 + len, a[i]) - b;
        rt[i] = update (rt[i - 1], 1, len);
    }
    while (m --){
        int l, r, k;
        scanf ("%d %d", &l, &r);
        k = (r - l + 1) / 2 + 1;
        printf ("%d\n", b[query (rt[l - 1], rt[r], 1, len, k)]);
    }
    return 0;
}

謝謝!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章