浅析主席树

由于作者太蒟了,不会动态主席树,所以这里就只讲静态主席树。其实会了平衡树什么问题都没了。

一.定义

可持久化线段树它就牛逼在一个别名:主席数
什么叫可持久化呢?我也无法解释。其实主席树就是用来解决区间第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;
}

谢谢!

发布了61 篇原创文章 · 获赞 32 · 访问量 8329

猜你喜欢

转载自blog.csdn.net/weixin_43908980/article/details/103889676