主席树

目录:

  • 我的理解
  • 时空复杂度分析
  • 应用及例题
  • 拓展

1、我的理解:

主席树的全称是可持续化权值线段树,是一种能够维护静态区间第K小的高级数据结构。html

主席树的主要思想就是:保存每次插入操做时的历史版本,以便查询区间第 \(k\) 小。c++

由于主席树每次都要插入操做,因此是不能用堆式建树的(rt<<1 ,rt<<1|1),因此咱们使用动态开点线段树,并用ls[]rs[]保存当前节点的左右儿子。数组

联系前缀和,能够预处理达到 \(O(1)\) 的时间复杂度。咱们发现主席树也知足这个性质,因此若须要统计 \([l,r]\) 的信息,只须要 \(O(1)\) 查询sum[r]-sum[l-1]便可。数据结构


2、时空复杂度分析:

  1. 时间复杂度:ui

    同线段树的时间复杂度:\(O(n\text{log}n)\)spa

  2. 空间复杂度:code

    咱们是动态开点,因此一颗线段树只会出现 \(2n-1\) 个节点,而每次插入会增长 \(O(n\text{log}n)\) 个节点,则最坏状况下会有 \(2n-1+O(n\text{log}n)\) 个节点。故空间复杂度为:\(O(n\text{log}n)\)htm

    在实际运用的时候,ls[],rs[],sum[]等数组都须要开 \(2^5\) 倍空间,即MAXN<<5(MAXN\(n\) 的值域)。blog

    :在常数上减少内存消耗:递归

    插入值时候先不要一次新建到底,能留住就留住,等到须要访问子节点时候再建下去。

    这样理论内存复杂度依然是\(O(n\text{log}n)\),但由于实际上不少结点在查询时候根本没用到,因此内存能少用一些。

    ————cyendra


3、应用及例题:

  1. 静态区间第K小(P3834 【模板】可持久化线段树 1(主席树))

    Description:

    给定数列 \(\{a_n\}\) ,求闭区间 \([l,r]\) 的第 \(k\) 小的数。

    Method:

    先对数据进行离散化,而后按照权值创建线段树。

    若要寻找 \([1,p]\) 的第 \(k\) 小,则从根节点开始处理。定义\(Son_{left}\) 表示左儿子的集合,\(Son_{right}\) 表示右儿子的集合。若 \(|Son_{left}|\ge k\) 时,说明第\(k\)小的数在左子树中,以左儿子为新的根向下递归更新,寻找左子树中第 \(k\) 小的数;反之,说明第\(k\)小的数在右子树中,以左儿子为新的根向下递归更新,寻找左子树中第 \(k-|Son_{left}|\) 小的数。

    拓展一下,咱们先预处理建树,获得 \(n+1\) 个版本的线段树(包括初始的线段树),编号为 \(0 \sim n\)

    前文提到过,主席树知足前缀和查询的思想,故咱们要求 \([l,r]\) 的第 \(k\) 小值,便可用sum[r]-sum[l-1]

    Code:

    #include<bits/stdc++.h>
    #define int long long 
    #define Maxn 200010
    using namespace std;
    inline void read(int &x)
    {
        int f=1;x=0;char s=getchar();
        while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
        while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
        x*=f;
    }
    int n,m;
    struct Segtree
    {
     int ls,rs,sum;
    }tree[Maxn<<5];
    int rt[Maxn];
    int a[Maxn],ins[Maxn]; 
    int len,tot=0;
    inline void Init(){tot=0;}
    inline int getid(const int &x)
    {
     return lower_bound(ins+1,ins+len+1,x)-ins;
    }
    inline void pushup(int rt)
    {
     tree[rt].sum=tree[tree[rt].ls].sum+tree[tree[rt].rs].sum;
    }
    inline int build(int l,int r)
    {
     int rt=++tot;
     if(l==r) 
     {
         tree[rt].sum=0;
         return rt;
     }
     int mid=(l+r)/2;
     tree[rt].ls=build(l,mid);
     tree[rt].rs=build(mid+1,r);
     pushup(rt);
     return rt;
    }
    int update(int k,int l,int r,int root,int val)
    {
     int rt=++tot;
     tree[rt]=tree[root];
     if(l==k&&r==k)
     {
         tree[rt].sum+=val;
         return rt;
     }
     int mid=(l+r)/2;
     if(k<=mid) tree[rt].ls=update(k,l,mid,tree[rt].ls,val);
     else tree[rt].rs=update(k,mid+1,r,tree[rt].rs,val);
     pushup(rt);
     return rt;
    }
    int query(int u,int v,int l,int r,int k)
    {
     if(l==r) return l;
     int mid=(l+r)/2,x=tree[tree[v].ls].sum-tree[tree[u].ls].sum;
     if(k<=x) return query(tree[u].ls,tree[v].ls,l,mid,k);
     else return query(tree[u].rs,tree[v].rs,mid+1,r,k-x);
    }
    signed main()
    {
     Init();
     read(n),read(m);
     for(int i=1;i<=n;i++)
     {
         read(a[i]);
     }
     memcpy(ins,a,sizeof(ins));
     sort(ins+1,ins+n+1);
     len=unique(ins+1,ins+n+1)-ins-1;
     rt[0]=build(1,len);
     for(int i=1;i<=n;i++)
     {
         rt[i]=update(getid(a[i]),1,len,rt[i-1],1);
     }
     while(m--)
     {
         int l,r,k;
         read(l),read(r),read(k);
         printf("%lld\n",ins[query(rt[l-1],rt[r],1,len,k)]);
     }
     return 0;
    }

    Warning:

    • ls[],rs[],sum[]等数组都要乘上 \(2^5\)
    • 离散化取lower_bound时,是最后减去0开头的地址,而不是1开头的地址。(便是lower_bound(ins+1,ins+n+1,x)-ins,而不是lower_bound(ins+1,ins+n+1,x)-ins-1
    • 查询时递归右子树时查找第 \(k-|Son_{left}|\) 小,而不是 \(k\) 小。
  2. 静态区间互异的个数(SP3267 DQUERY - D-query

    Description:

    给定数列 \(\{a_n\}\) ,求闭区间 \([l,r]\) 的互异的个数。

    Method:

    扫描序列创建可持续化线段树,若此元素是第一次出现,就将对应的线段树中的位置加1;反之,就将上一个出现的元素对应的线段树中的位置减1,将此元素对应的线段树中的位置加1。

    对于查询的 \([l,r]\) ,在第 \(r\) 个版本的线段树上查询位置 \(l\) ,对通过的区间中的和累加一下便可。

    Code:

    #include<bits/stdc++.h>
    #define int long long 
    #define Maxn 30010
    using namespace std;
    inline void read(int &x)
    {
        int f=1;x=0;char s=getchar();
        while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
        while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
        x*=f;
    }
    int n,q;
    int a[Maxn]; 
    int tot=0;
    struct Segtree
    {
     int ls,rs,sum;
    }tree[Maxn<<5];
    int rt[Maxn];
    inline void Init(){tot=0;}
    inline void pushup(int rt)
    {
     tree[rt].sum=tree[tree[rt].ls].sum+tree[tree[rt].rs].sum;
    }
    inline int build(int l,int r)
    {
     int rt=++tot;
     if(l==r) 
     {
         tree[rt].sum=0;
         return rt;
     }
     int mid=(l+r)/2;
     tree[rt].ls=build(l,mid);
     tree[rt].rs=build(mid+1,r);
     pushup(rt);
     return rt;
    }
    int update(int k,int l,int r,int root,int val)
    {
     int rt=++tot;
     tree[rt]=tree[root];
     if(l==k&&r==k)
     {
         tree[rt].sum+=val;
         return rt;
     }
     int mid=(l+r)/2;
     if(k<=mid) tree[rt].ls=update(k,l,mid,tree[rt].ls,val);
     else tree[rt].rs=update(k,mid+1,r,tree[rt].rs,val);
     pushup(rt);
     return rt;
    }
    int query(int l,int r,int rt,int pos)
    {
     if(l==r) return tree[rt].sum;
     int mid=(l+r)/2;
     if(pos<=mid) return tree[tree[rt].rs].sum+query(l,mid,tree[rt].ls,pos);
     else return query(mid+1,r,tree[rt].rs,pos);
    }
    map<int,int>mp;
    signed main()
    {
     Init();
     read(n);
     for(int i=1;i<=n;i++)
     {
         read(a[i]);
     }
     rt[0]=build(1,n);
     for(int i=1;i<=n;i++)
     {
         if(mp.find(a[i])==mp.end())
         {
             mp[a[i]]=i;
             rt[i]=update(i,1,n,rt[i-1],1);  
         }else
         {
             int tmprt=update(mp[a[i]],1,n,rt[i-1],-1);
             rt[i]=update(i,1,n,tmprt,1);
         }
         mp[a[i]]=i;
     }
     read(q);
     while(q--)
     {
         int l,r;
         read(l),read(r);
         int ans=query(1,n,rt[r],l);
         printf("%lld\n",ans);
     } 
     return 0;
    }

4、扩展

  1. 动态区间第K小

    Description:

    给定数列 \(\{a_n\}\) ,支持两种操做:

    • \(pos\) 位置的值改成 \(p\)
    • 查询闭区间 \([l,r]\) 的第 \(k\) 小值。

    Mothod

    考虑树套树(树状数组套主席树)

    咕咕咕……

相关文章
相关标签/搜索
本站公众号
   欢迎关注本站公众号,获取更多信息