主席树

主席树

主席树,就是可持久化权值线段树,也叫函数式线段树

引入

考虑如下问题:

给定一个数列,查询其中第k大值

显然,我们可以建一棵权值线段树,直接在上面二分就好了,即对于每个结点,查看它左子树的结点数量是否大于k,设为 \(sum\) 如果 \(sum \ge k\) ,则第k个结点在其左子树中,否则就是其右子树的第 \(k-sum\) 个结点,递归一直到叶子即可。

考虑第二个问题:

给定一个数列,查询前缀第k大值

我们考虑对于每个前缀建一棵权值线段树,重复上述做法,但是这样预处理(即建树)时空复杂度是 \(O(n^2 \log n)\) 的,因为我们要建n棵树。但是我们发现这几棵树每一棵都只比上一棵多一个结点,所以我们考虑用一种更加强大的建树法。

建树(静态主席树)

一棵动态开点的权值线段树,它可能是这样的(左边编号为0-14的完全二叉树,当然,不一定是完全二叉树)

我们发现,假如说我们要下一个前缀在上一个基础上修改12号结点,我们直接像这样重开一条链,剩下部分指回原来没有改的结点就好,因为下面都是完全一样的,从新的根(15)开始便利,就可以找到这棵改完的树了,时空复杂度 \(O(n \log n)\) (其实不离散化的情况下是 \(O(n \log siz)\) ,siz是值域)

具体实现就是建立结点的时候同步遍历上一个版本的树,然后复制一个结点之后改一个分支就好

建树代码:

void build(int &now,int old,int st,int ed,int x)
    {
        node[++sz]=node[old],ls[sz]=ls[old],rs[sz]=rs[old],now=sz;
        if(st==ed)  {node[now]++;return;}
        if(x<=mid)  build(ls[now],ls[old],st,mid,x);
        if(x>mid)   build(rs[now],rs[old],mid+1,ed,x);
        node[now]=node[ls[now]]+node[rs[now]];
    }

好了,这就是主席树核心部分了,剩下的查询和普通树一样了

我们发现它维护的实质上是前缀,所以说对于区间操作,只能维护可差分信息
贴个板子题K小数代码

CODE
#include<bits/stdc++.h>
using namespace std;
#define N 201000
long long n,q,a[N],root[N],p,b,c;
struct SEGMENT_TREE
{
    #define mid ((st+ed)>>1)
    long long node[N*100],ls[N*100],rs[N*100],sz;
    void build(long long &now,long long &old,long long st,long long ed,long long x)
    {
        node[++sz]=node[old],ls[sz]=ls[old],rs[sz]=rs[old],now=sz;
        if(st==ed)  {node[now]++;return;}
        if(x<=mid)  build(ls[now],ls[old],st,mid,x);
        if(x>mid)   build(rs[now],rs[old],mid+1,ed,x);
        node[now]=node[ls[now]]+node[rs[now]];
    }
    long long find(long long &x,long long &y,long long st,long long ed,long long rk)
    {
        long long sum=node[ls[y]]-node[ls[x]];
        if(st==ed)     return st;
        if(rk<=sum)    return find(ls[x],ls[y],st,mid,rk);
        if(rk>sum)     return find(rs[x],rs[y],mid+1,ed,rk-sum);
        return 0;
    }
    #undef mid
}s_tree;
int main()
{
    #ifndef ONLINE_JUDGE
        freopen("1.in","r",stdin);
        freopen("1.out","w",stdout);
    #endif
    scanf("%lld%lld",&n,&q);
    for(int i=1;i<=n;i++)   scanf("%lld",&a[i]),s_tree.build(root[i],root[i-1],1,2e9,a[i]+1e9);
    for(int i=1;i<=q;i++)
    {
        scanf("%lld%lld%lld",&p,&b,&c);
        printf("%lld\n",s_tree.find(root[p-1],root[b],1,2e9,c)-(long long)1e9);
    }
    return 0;
}

进阶操作:区间修改

这个不会很重要,比如二维数点维护线段之类的,标记永久化就好了,和普通线段树永久化差不了太多

动态主席树

依旧是模板题,但是带修,比如 树套树

我们发现主席树不支持修改一个值,因为它对后面都有影响。

考虑在最外层套一个树状数组,它每个结点都是普通线段树,时空复杂度都是 \(O((n+m) \log^2 (n+m))\) ,可以在初始序列用主席树优化建树。这做法虽然空间复杂度劣,但是常数非常小,时间吊打线段树套平衡树之类做法

CODE
#include<bits/stdc++.h>
#define N 1001000 
#define L 50010
#define llt long long
using namespace std; 
llt n,m,a,b,c,s[L],R[L];char op;
#define mid ((st+ed)>>1)
struct SEGMENT_TREE
{
    llt sz,ls[N*30],rs[N*30],node[N*30];
    void build(llt &now,llt st,llt ed,llt x,llt o)
    {
        if(!now) now=++sz;
        if(st==ed)  {node[now]+=o;return;}
        if(x<=mid)  build(ls[now],st,mid,x,o);
        if(x>mid)   build(rs[now],mid+1,ed,x,o);
        node[now]=node[ls[now]]+node[rs[now]];
    }
}S;
struct PRESIDENT_TREE
{
    llt sz,ls[N*30],rs[N*30],node[N*30];
    void build(llt &now,llt old,llt st,llt ed,llt x)
    {
        now=++sz;node[now]=node[old],ls[now]=ls[old],rs[now]=rs[old];
        if(st==ed)  {node[now]++;return;}
        if(x<=mid)  build(ls[now],ls[old],st,mid,x);
        if(x>mid)   build(rs[now],rs[old],mid+1,ed,x);
        node[now]=node[ls[now]]+node[rs[now]];
    }
}P;
#define tol for(int i=1;i<=la;i++)  a[i]=S.ls[a[i]];for(int i=1;i<=lb;i++)  b[i]=S.ls[b[i]];
#define tor for(int i=1;i<=la;i++)  a[i]=S.rs[a[i]];for(int i=1;i<=lb;i++)  b[i]=S.rs[b[i]];
llt find(llt x,llt y,llt* a,llt *b,llt la,llt lb,llt st,llt ed,llt rk)
{
    llt sum=P.node[P.ls[x]]-P.node[P.ls[y]];
    for(int i=1;i<=la;i++)  sum+=S.node[S.ls[a[i]]];
    for(int i=1;i<=lb;i++)  sum-=S.node[S.ls[b[i]]];
    if(st==ed)  return st;
    if(sum>=rk) {tol return find(P.ls[x],P.ls[y],a,b,la,lb,st,mid,rk);}
    else {tor return find(P.rs[x],P.rs[y],a,b,la,lb,mid+1,ed,rk-sum);}
}
llt find_rk(llt x,llt y,llt* a,llt *b,llt la,llt lb,llt st,llt ed,llt num)
{
    llt sum=P.node[P.ls[x]]-P.node[P.ls[y]];
    for(int i=1;i<=la;i++)  sum+=S.node[S.ls[a[i]]];
    for(int i=1;i<=lb;i++)  sum-=S.node[S.ls[b[i]]];
    if(st==ed)  return 1;
    if(num<=mid) {tol return find_rk(P.ls[x],P.ls[y],a,b,la,lb,st,mid,num);}
    else {tor return find_rk(P.rs[x],P.rs[y],a,b,la,lb,mid+1,ed,num)+sum;}
}
#undef mid
struct BIT
{
    #define lowbit(x)   (x&(-x))
    llt root[L],u[L],v[L];
    llt check(llt x,llt y,llt rk)
    {
        if(rk==0)   return -2147483647;
        if(rk>y-x+1)    return 2147483647;
        llt p1=0,p2=0;x--; 
        for(int i=y;i>=1;i-=lowbit(i))
            v[++p1]=root[i];
        for(int i=x;i>=1;i-=lowbit(i))
            u[++p2]=root[i];
        return find(R[y],R[x],v,u,p1,p2,-1e8,1e8,rk);
    }
    llt check_rk(llt x,llt y,llt num)
    {
        llt p1=0,p2=0;x--; 
        for(int i=y;i>=1;i-=lowbit(i))
            v[++p1]=root[i];
        for(int i=x;i>=1;i-=lowbit(i))
            u[++p2]=root[i];
        return find_rk(R[y],R[x],v,u,p1,p2,-1e8,1e8,num);
    }
    llt Nxt(llt x,llt y,llt num){return check(x,y,check_rk(x,y,num+1));}
    llt Pre(llt x,llt y,llt num){return check(x,y,check_rk(x,y,num)-1);}
    void change(llt x,llt is,llt p)
    {
        for(int i=x;i<=n;i+=lowbit(i))
            S.build(root[i],-1e8,1e8,is,p);
    }
}bit;
int main()
{
    #ifndef ONLINE_JUDGE
        freopen("1.in","r",stdin);
        freopen("1.out","w",stdout);
    #endif 
    scanf("%lld%lld",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%lld",&s[i]),P.build(R[i],R[i-1],-1e8,1e8,s[i]);
    for(int i=1;i<=m;i++)
    {
        scanf(" %c",&op);
        if(op=='1'){scanf("%lld%lld%lld",&a,&b,&c);printf("%lld\n",bit.check_rk(a,b,c));}
        if(op=='2'){scanf("%lld%lld%lld",&a,&b,&c);printf("%lld\n",bit.check(a,b,c));}
        if(op=='3'){scanf("%lld%lld",&a,&b);bit.change(a,s[a],-1);s[a]=b;bit.change(a,s[a],1);}
        if(op=='4'){scanf("%lld%lld%lld",&a,&b,&c);printf("%lld\n",bit.Pre(a,b,c));}
        if(op=='5'){scanf("%lld%lld%lld",&a,&b,&c);printf("%lld\n",bit.Nxt(a,b,c));}
    }
    return 0;
}
这就差不多是主席树基本内容了,有时间补题解
posted @ 2024-05-25 14:57  wang54321  阅读(11)  评论(0编辑  收藏  举报