【算法】 口胡主席树

  “可持久化”几个字限制我的想象力,导致我长久没有去接触这种强大的数据结构,主席树的思想如下:

  在线段树中,不难发现,每次修改与查询的时间复杂度都是o(logn)的,这是因为每次操作所要调用与修改的节点也是logn个。

  在一些题目中,题目要求我们查询线段树(尤其是值域线段树)的历史版本,这就可以用到主席树这一数据结构,每次修改都只新增logn个新节点,并记录下来每次修改后根的位置,每次查询都从某版本的根开始,就可以方便快捷的操作,同时把时间复杂度卡死在o(logn)。

   题目:区间第K大

  主席树的经典题,运用一点前缀和的思想。A1~A2,A1~A2......A1~An每个区间建立起一棵值域线段树。把数组中每一个数看作插入操作,从头到尾相当于建立了n个版本的主席树。每次查询的时候,一个区间内数的个数便可以用差分求出,然后用线段树常规求第K大可以了。

  代码(粪山,LC原话):

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#define maxn 100005*160
#define lc(a1) t[a1].ch[0]
#define rc(a1) t[a1].ch[1]
#define sum(a1) t[a1].sum
#define left(a1) t[a1].left
#define right(a1) t[a1].right

struct hjt_tree {
    int ch[2],sum,left,right;
}t[maxn];
int a[maxn],b[maxn],n,m,size,rt[maxn];

void change(int,int,int);
int query(int,int,int);
void build(int,int,int);

int main() {
    scanf("%d%d",&n,&m);
    for (int i = 1;i <= n;i++) scanf("%d",&a[i]), b[i] = a[i];
    std::sort(b + 1,b + n + 1);
    int len = std::unique(b + 1,b + n + 1) - b - 1;
    build(rt[0] = 0,1,n);
    for (int i = 1;i <= n;i++) {
        int loc = std::lower_bound(b + 1,b + len + 1,a[i]) - b;
        change(rt[i] = ++size,rt[i - 1],loc);
    }
    for (int i = 1;i <= m;i++) {
        int x,y,z;
        scanf("%d%d%d",&x,&y,&z);
        printf("%d\n",b[query(rt[x - 1],rt[y],z)]);
    }
    return 0;
}

void build(int x,int l,int r) {
    sum(x) = 0; left(x) = l; right(x) = r;

    if (l == r) return ;

    int mid = (l + r) / 2;
    build(lc(x) = ++size,l,mid);
    build(rc(x) = ++size,mid + 1,r);
}
void change(int x,int ori,int loc) {
    sum(x) = sum(ori) + 1; lc(x) = lc(ori); rc(x) = rc(ori);
    left(x) = left(ori); right(x) = right(ori);

    if (left(x) == right(x)) return ;

    int mid = (left(x) + right(x)) / 2;
     if (loc <= mid) 
        change(lc(x) = ++size,lc(ori),loc);
    else  
        change(rc(x) = ++size,rc(ori),loc);
}
int query(int x,int y,int k) {
    int know = sum(lc(y)) - sum(lc(x));

    if (left(x) == right(x)) return left(x);

    int mid = (left(x) + right(x)) / 2;
    if (k <= know)
        return query(lc(x),lc(y),k);
    else
        return query(rc(x),rc(y),k-know);
}
原文地址:https://www.cnblogs.com/Illusions/p/13567292.html