zoukankan      html  css  js  c++  java
  • 主席树摘要

    介绍&主要用途

    主席树,据说由fotile大佬因不会划分树而发明。由于大佬名字缩写为HJT而被Seter大佬命名为主席树。

    主要用于查询区间第k值。

    原理

    首先离散化,对每个节点i建一棵[1,i]的权值线段树。这里用到了前缀和思想并且满足可加减性。但这会MLE。然后我们发现,对于每个点的线段树,它和前面那个节点的线段树有且仅有logn个节点是不同的,因此我们可以借用前一个节点的线段树,仅仅新建logn个节点。这个思想成为了主席树和所有可持久化数据结构的思想基础。

    变量

    1 int n,m;
    2 int a[maxn];                                                                                //初始数组
    3 int lch[maxn],all;                                                                            //离散化
    4 struct Node {
    5     int L,R,sum;                                                                            //左儿子、右儿子编号和在此区间内的数的个数
    6 } tree[maxn*16];
    7 int ins;
    8 int root[maxn];                                                                                //每个节点的线段树的根
    var

    建树

    整个序列直接建树有点困难(也可能是我太菜了),考虑依次插入每个数。

    如果到叶子节点就在前一个点的基础上更新并直接返回,否则为相应的儿子分配新节点。

     1 void insert(int now,int l,int r,int x,int pla) {                                            //now为前一个点的当前编号,pla为分配节点编号
     2     if(l==r) {
     3         tree[pla].sum=tree[now].sum+1;                                                        //在前一个点的基础上更新节点
     4         return;
     5     }
     6     int mid=l+r>>1;
     7     if(x>mid) {
     8         tree[pla].L=tree[now].L;                                                            //复制左儿子
     9         insert(tree[now].R,mid+1,r,x,(tree[pla].R=++ins));                                    //新建右儿子并向右走
    10     } else {
    11         tree[pla].R=tree[now].R;                                                            //复制右儿子
    12         insert(tree[now].L,l,mid,x,(tree[pla].L=++ins));                                    //新建左儿子并向左走
    13     }
    14     update(pla);
    15 }
    insert

    查询区间[l,r]第k小

    利用前缀和思想,传入l-1和r,相减即可。

    1 int query(int l,int r,int z,int y,int k) {
    2     if(l==r) return l;                                                                        //叶子节点就是答案
    3     int mid=l+r>>1;
    4     return (k>tree[tree[y].L].sum-tree[tree[z].L].sum)?
    5            query(mid+1,r,tree[z].R,tree[y].R,k-(tree[tree[y].L].sum-tree[tree[z].L].sum)):    //左边太少,往右走
    6            query(l,mid,tree[z].L,tree[y].L,k);                                                //左边太多,往左走
    7 }
    query

    时空复杂度

    时间复杂度

    建树:O(logn)

    单次查询:和线段树一样O(logn)

    空间复杂度

    由于每个值仅出现logn次,有n个值,因此空间复杂度为O(nlogn)

    题目

    洛谷P3834 【模板】可持久化线段树 1(主席树)

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 #define INF 0x7fffffff
     4 #define ME 0x7f
     5 #define FO(s) freopen(s".in","r",stdin);freopen(s".out","w",stdout)
     6 #define fui(i,a,b,c) for(int i=(a);i<=(b);i+=(c))
     7 #define fdi(i,a,b,c) for(int i=(a);i>=(b);i-=(c))
     8 #define fel(i,a) for(register int i=h[a];i;i=ne[i])
     9 #define ll long long
    10 #define MEM(a,b) memset(a,b,sizeof(a))
    11 #define maxn (200000+10)
    12 int n,m;
    13 int a[maxn];
    14 int lch[maxn],all;
    15 struct Node{
    16     int L,R,sum;
    17 }tree[maxn*16];
    18 int ins;
    19 int root[maxn];
    20 template<class T>
    21 inline T read(T &n){
    22     n=0;int t=1;double x=10;char ch;
    23     for(ch=getchar();!isdigit(ch)&&ch!='-';ch=getchar());(ch=='-')?t=-1:n=ch-'0';
    24     for(ch=getchar();isdigit(ch);ch=getchar()) n=n*10+ch-'0';
    25     if(ch=='.') for(ch=getchar();isdigit(ch);ch=getchar()) n+=(ch-'0')/x,x*=10;
    26     return (n*=t);
    27 }
    28 template<class T>
    29 T write(T n){
    30     if(n<0) putchar('-'),n=-n;
    31     if(n>=10) write(n/10);putchar(n%10+'0');return n;
    32 }
    33 template<class T>
    34 T writeln(T n){write(n);putchar('
    ');return n;}
    35 void update(int x){tree[x].sum=tree[tree[x].L].sum+tree[tree[x].R].sum;}
    36 void insert(int now,int l,int r,int x,int pla){
    37     if(l==r){tree[pla].sum=tree[now].sum+1;return;}int mid=l+r>>1;
    38     if(x>mid){tree[pla].L=tree[now].L;insert(tree[now].R,mid+1,r,x,(tree[pla].R=++ins));}
    39     else{tree[pla].R=tree[now].R;insert(tree[now].L,l,mid,x,(tree[pla].L=++ins));}update(pla);
    40 }
    41 int query(int l,int r,int z,int y,int k){
    42     if(l==r) return l;int mid=l+r>>1;
    43     return (k>tree[tree[y].L].sum-tree[tree[z].L].sum)?
    44         query(mid+1,r,tree[z].R,tree[y].R,k-(tree[tree[y].L].sum-tree[tree[z].L].sum)):
    45         query(l,mid,tree[z].L,tree[y].L,k);
    46 }
    47 int main(){
    48     read(n);read(m);
    49     fui(i,1,n,1) lch[i]=read(a[i]);
    50     sort(lch+1,lch+n+1);all=unique(lch+1,lch+n+1)-lch-1;
    51     fui(i,1,n,1){
    52         int p=lower_bound(lch+1,lch+all+1,a[i])-lch;
    53         insert(root[i-1],1,all,p,(root[i]=++ins));
    54     }
    55     fui(i,1,m,1){
    56         int l,r,k;
    57         read(l);read(r);read(k);
    58         writeln(lch[query(1,all,root[l-1],root[r],k)]);
    59     }
    60     return 0;
    61 }
    AC代码
    作者:A星际穿越
    我的博客写得这么烂,应该不会有人想转载的吧
    如果要转载的话,请在文章显眼处标明作者和出处谢谢
  • 相关阅读:
    无法加载模块 TP3.2
    always_populate_raw_post_data
    关于(void**)及其相关的理解
    面向对象设计原则
    数据对齐总结
    C++ POD类型
    do..while(false)的用法总结
    c++为什么定义了析构函数的类的operator new[]传入的参数会多4字节?
    C++ new new[]详解
    【转】C内存操作函数
  • 原文地址:https://www.cnblogs.com/Axjcy/p/9534091.html
Copyright © 2011-2022 走看看