可持久化线段树

可持久化线段树,又称主席树,通常用于维护序列和值域。

对于单点修改的可持久化线段树,它主要的思想就是每次新建一个版本,复制一条链。

单点修改和查询历史版本。不能再用\(lc\)\(rc\)记录根节点的左右子树,需要用变量来记录,每次修改最多复制\(O(logN)\)的链。

struct PersistentSegmentTree{
    struct tree{
        int l,r,v;
    }t[N<<5];
    int tot,root[N];/*历史版本*/
    #define l(p) (t[p].l)
    #define r(p) (t[p].r)
    #define v(p) (t[p].v)
    void build(int&p,int l,int r,int a[]){
        p=++tot;/*新建节点*/
        if(l==r){
            v(p)=a[l];/*赋值*/
            return;
        }
        int mid=l+r>>1;
        build(l(p),l,mid,a);
        build(r(p),mid+1,r,a);
    }
    void insert(int&p,int q,int l,int r,int x,int v){
        p=++tot;
        t[p]=t[q];/*复制这条链*/
        if(l==r){
            v(p)=v;/*单点修改*/
            return;
        }
        int mid=l+r>>1;
        if(x<=mid)insert(l(p),l(q),l,mid,x,v);
        else insert(r(p),r(q),mid+1,r,x,v);
    }
    int query(int p,int l,int r,int x){
        if(l==r)return v(p);
        int mid=l+r>>1;
        if(x<=mid)return query(l(p),l,mid,x);
        else return query(r(p),mid+1,r,x);
    }
    inline void modify(int x,int y){
        root[x]=root[y];
    }
}pst;

静态区间第k小。对于序列的每个节点都用一个权值线段树维护,记录\([1,i\)]内每个数字出现的次数,也就是前缀和,在查询时前缀和可以相减,于是将区间内值域逐渐缩小即可。

struct PersistentSegmentTree{
    struct tree{
        int l,r,sum;
    }t[N<<5];
    int tot,root[N];
    #define l(p) (t[p].l)
    #define r(p) (t[p].r)
    #define s(p) (t[p].sum)
    inline void pushup(int p){
        s(p)=s(l(p))+s(r(p));/*前缀和*/
    }
    void build(int&p,int l,int r){
        p=++tot;
        if(l==r)return;
        int mid=l+r>>1;
        build(l(p),l,mid);
        build(r(p),mid+1,r);
    }
    void insert(int&p,int q,int l,int r,int x){
        p=++tot;
        t[p]=t[q];
        s(p)++;/*更新前缀和*/
        if(l==r)return;
        int mid=l+r>>1;
        if(x<=mid)insert(l(p),l(q),l,mid,x);
        else insert(r(p),r(q),mid+1,r,x);
        pushup(p);
    }
    int kth(int p,int q,int l,int r,int k,int a[]){
        if(l==r)return a[l];/*找到最后的一个点*/
        int mid=l+r>>1,x=s(l(p))-s(l(q));/*值域范围内的数出现的次数*/
        if(k<=x)return kth(l(p),l(q),l,mid,k,a);/*当前值域出现次数>=k,则一定在左子树*/
        else return kth(r(p),r(q),mid+1,r,k-x,a);/*递归右子树时减去当前值域出现次数*/
    }
}pst;

动态区间主席树。树状数组套权值线段树,每个点的修改会影响当前版本的根到最后一个版本的根,单点修改区间查询,可以使用树状数组。

#include<bits/stdc++.h>

using namespace std;

const int N=2e5+5;
int a[N],n,m;
struct PersistentSegmentTree{
    struct tree{
        int l,r,sum;
    }t[N<<8];
    int root[N],tot,rt[2][33];
    #define l(p) (t[p].l)
    #define r(p) (t[p].r)
    #define s(p) (t[p].sum)
    inline int lowbit(int x){
        return x&-x;
    }
    void insert(int&p,int l,int r,int x,int v){
        if(!p)p=++tot;
        s(p)+=v;
        if(l==r)return;
        int mid=l+r>>1;
        if(x<=mid)insert(l(p),l,mid,x,v);
        else insert(r(p),mid+1,r,x,v);
    }
    inline int kth(int p,int q,int k){
        int t1=0,t2=0,l=0,r=1e9;
        for(int i=p;i;i-=lowbit(i))rt[0][++t1]=root[i];
        for(int i=q;i;i-=lowbit(i))rt[1][++t2]=root[i];
        while(l<r){
            int re=0,mid=l+r>>1;
            for(int i=1;i<=t1;i++)re+=s(l(rt[0][i]));/*累加根在[p,n]的主席树左子树的和*/
            for(int i=1;i<=t2;i++)re-=s(l(rt[1][i]));/*差分根在[q,n]的主席树左子树的和*/
            if(k<=re){/*跳到左子树*/
                for(int i=1;i<=t1;i++)rt[0][i]=l(rt[0][i]);/*所有根都要跳过去*/
                for(int i=1;i<=t2;i++)rt[1][i]=l(rt[1][i]);
                r=mid;
            }
            else{/*跳到右子树*/
                for(int i=1;i<=t1;i++)rt[0][i]=r(rt[0][i]);/*所有根都要跳过去*/
                for(int i=1;i<=t2;i++)rt[1][i]=r(rt[1][i]);
                k-=re;/*减去左子树的大小*/
                l=mid+1;
            }
        }
        return l;
    }
    inline void modify(int x,int v){
        for(int i=x;i<=n;i+=lowbit(i))insert(root[i],0,1e9,a[x],-1);
        a[x]=v;
        for(int i=x;i<=n;i+=lowbit(i))insert(root[i],0,1e9,a[x],1);
    }
}seg;
int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        for(int j=i;j<=n;j+=j&-j)seg.insert(seg.root[j],0,1e9,a[i],1);
    }
    while(m--){
        string s;
        int x,y,k;
        cin>>s>>x>>y;
        if(s=="Q")cin>>k,cout<<seg.kth(y,x-1,k)<<'\n';
        else seg.modify(x,y);
    }
    return 0;
}

求区间\(lcm\)

一段区间的\(lcm\)是由区间乘积再除上区间两两\(gcd\)构成的,将每个数质因数分解,对于质因数\(p\)\(x\)次幂,可以找到上一个\(x\)所在上一个位置,在该位置乘\(x\)的逆元,这样就除掉了两个数的\(gcd\),注意初始化可持久化线段树根节点的权值要是\(1\),即\(t[0].v=1\).

struct PersistSegmentTree{
    struct tree{
        int l,r,v;
    }t[N*400];
    int tot,root[N];
    #define l(p) (t[p].l)
    #define r(p) (t[p].r)
    #define v(p) (t[p].v)
    inline void pushup(int p){
        v(p)=1ll*v(l(p))*v(r(p))%mod;
    }
    void insert(int&p,int q,int l,int r,int x,int v){
        p=++tot;
        t[p]=t[q];
        if(l==r)return v(p)=1ll*v*v(p)%mod,void();
        int mid=l+r>>1;
        if(x<=mid)insert(l(p),l(q),l,mid,x,v);
        else insert(r(p),r(q),mid+1,r,x,v);
        pushup(p);
    }
    int query(int p,int l,int r,int x){
        if(l>=x)return v(p);
        if(r<x)return 1;
        int mid=l+r>>1;
        return 1ll*query(l(p),l,mid,x)*query(r(p),mid+1,r,x)%mod;
    }
}ps;
    for(int i=1;i<=n;i++){
        int x;
        cin>>x;
        ps.root[i]=ps.root[i-1];
        while(miv[x]){
            int k=miv[x],t=1;
            while(x%k==0){
                t*=k;
                x/=k;
                if(pre[t])ps.insert(ps.root[i],ps.root[i],1,n,pre[t],inv[k]);
                pre[t]=i;
            }
            ps.insert(ps.root[i],ps.root[i],1,n,i,t);
        }
    }
    cin>>q;
    while(q--){
        int l,r;
        cin>>l>>r;
        l=(l+ans)%n+1,r=(r+ans)%n+1;
        if(l>r)swap(l,r);
        cout<<(ans=ps.query(ps.root[r],1,n,l))<<'\n';
    }

从区间\([l,r]\)内取出一个长度为k的区间,求区间最小值的最大值。

考虑二分区间最小值,将\(>=mid\)的视作\(1\)\(<mid\)的视作\(0\),可以转化为区间内是否存在一个长度不小于\(k\)的连续的\(1\)段,离散化后用可持久化线段树,从大到小依次插入数字所处的位置。

struct PersistSegmentTree{
    struct tree{
        int len,lma,rma,tma;
        inline tree(){len=lma=rma=tma=0;}
        inline tree(int len,int lma,int rma,int tma):len(len),lma(lma),rma(rma),tma(tma){}
        inline tree operator+(const tree&rhs){
            return (tree){len+rhs.len,lma==len?lma+rhs.lma:lma,rhs.rma==rhs.len?rhs.rma+rma:rhs.rma,max({tma,rhs.tma,rma+rhs.lma})};
        }
    }t[N<<5];
    int tot,root[N],lc[N<<5],rc[N<<5];
    #define l(p) (lc[p])
    #define r(p) (rc[p])
    #define le(p) (t[p].len)
    #define lm(p) (t[p].lma)
    #define rm(p) (t[p].rma)
    #define t(p) (t[p].tma)
    void build(int&p,int l,int r){
        p=++tot;
        if(l==r)return le(p)=1,void();
        int mid=l+r>>1;
        build(l(p),l,mid);
        build(r(p),mid+1,r);
        t[p]=t[l(p)]+t[r(p)];
    }
    void insert(int&p,int q,int l,int r,int x){
        p=++tot;
        l(p)=l(q);
        r(p)=r(q);
        t[p]=t[q];
        if(l==r){
            le(p)=lm(p)=rm(p)=t(p)=1;
            return;
        }
        int mid=l+r>>1;
        if(x<=mid)insert(l(p),l(q),l,mid,x);
        else insert(r(p),r(q),mid+1,r,x);
        t[p]=t[l(p)]+t[r(p)];
    }
    tree query(int p,int l,int r,int x,int y){
        if(x<=l&&r<=y)return t[p];
        int mid=l+r>>1;
        tree re;
        if(x<=mid)re=re+query(l(p),l,mid,x,y);
        if(mid<y)re=re+query(r(p),mid+1,r,x,y);
        return re;
    }
    inline int query(int x,int y,int k){
        int l=1,r=n,re=0;
        while(l<=r){
            int mid=l+r>>1;
            if(query(root[mid],1,n,x,y).tma>=k)r=mid-1,re=mid;
            else l=mid+1;
        }
        return re;
    }
}pst;
struct node{
    int val,id;
    inline friend bool operator<(const node&a,const node&b){return a.val>b.val;}
}a[N];
int main(){
    cin>>n;
    for(int i=1;i<=n;i++)cin>>a[i].val,a[i].id=i;
    sort(a+1,a+1+n);
    pst.build(pst.root[0],1,n);
    for(int i=1;i<=n;i++)pst.insert(pst.root[i],pst.root[i-1],1,n,a[i].id),cout<<a[i].val<<' '<<a[i].id<<'\n';
    int m;
    cin>>m;
    while(m--){
        int l,r,k;
        cin>>l>>r>>k;
        cout<<a[pst.query(l,r,k)].val<<'\n';
    }
    return 0;
}
posted @ 2022-11-14 18:06  半步蒟蒻  阅读(78)  评论(0)    收藏  举报