【模板整合计划】高阶数据结构—线段树

倖福魔咒の 提交于 2019-12-01 20:00:14

【模板整合计划】高阶数据结构—线段树

【学习笔记】线段树详解(全)

一:【基本操作及扩展】

1.【区间修改(+),区间查询(Sum)】

【模板】线段树 \(1\) \(\text{[P3372]}\)

#include<cstdio>
#define Re register int
#define LL long long
#define pl p<<1
#define pr p<<1|1
const int N=1e5+3;
struct QAQ{int l,r;LL S,add;}Q[N<<2];
int i,b,c,d,e,n,m,fu,a[N];
inline void creat(Re p,Re l,Re r){
    Q[p].l=l,Q[p].r=r;
    if(l==r){Q[p].S=a[l];return;}
    Re mid=l+r>>1;
    creat(pl,l,mid),creat(pr,mid+1,r);
    Q[p].S=Q[pl].S+Q[pr].S;
}
inline void spread(Re p){
    if(Q[p].add){
        LL a=Q[p].add;
        Q[pl].S+=a*(Q[pl].r-Q[pl].l+1);
        Q[pr].S+=a*(Q[pr].r-Q[pr].l+1);
        Q[pl].add+=a,Q[pr].add+=a,Q[p].add=0;
    }
}
inline void change(Re p,Re l,Re r,Re x){
    Re L=Q[p].l,R=Q[p].r;
    if(l<=L&&R<=r){Q[p].S+=(LL)x*(R-L+1),Q[p].add+=x;return;}
    Re mid=L+R>>1;spread(p);
    if(l<=mid)change(pl,l,r,x);
    if(r>mid)change(pr,l,r,x);
    Q[p].S=Q[pl].S+Q[pr].S;
}
inline LL ask(Re p,Re l,Re r){
    Re L=Q[p].l,R=Q[p].r;
    if(l<=L&&R<=r)return Q[p].S;
    Re mid=L+R>>1;LL ans=0;spread(p);
    if(l<=mid)ans+=ask(pl,l,r);
    if(r>mid)ans+=ask(pr,l,r);
    return ans;
}
inline void in(int &x){
    x=fu=0;char c=getchar();
    while(c<'0'||c>'9')fu|=c=='-',c=getchar();
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
    if(fu)x=-x;
}
int main(){
    in(n),in(m);
    for(i=1;i<=n;i++)in(a[i]);
    creat(1,1,n);
    while(m--){
        in(b),in(c),in(d);
        if(b>1)printf("%lld\n",ask(1,c,d));
        else in(e),change(1,c,d,e);
    }
}

2.【区间修改(+,×),区间查询(Sum)】

【模板】线段树 2 \(\text{[P3373]}\) / 维护序列 \(\text{[P2023]}\) \(\text{[BZOJ1798]}\)**

#include<cstdio>
#define LL long long
#define Re register LL
#define pl p<<1
#define pr p<<1|1
const int N=1e5+3;
struct QAQ{LL l,r,S,add,mul;}Q[N<<2];
LL i,b,c,d,e,n,m,P,fu,a[N];
inline void creat(Re p,Re l,Re r){
    Q[p].l=l,Q[p].r=r,Q[p].mul=1;
    if(l==r){Q[p].S=a[l]%P;return;}
    Re mid=l+r>>1;
    creat(pl,l,mid),creat(pr,mid+1,r);
    Q[p].S=(Q[pl].S+Q[pr].S)%P;
}
inline void spread(Re p){
    Re a=Q[p].add,m=Q[p].mul;
    ((Q[pl].S*=m)+=a*(Q[pl].r-Q[pl].l+1))%=P;
    ((Q[pr].S*=m)+=a*(Q[pr].r-Q[pr].l+1))%=P;
    (Q[pl].mul*=m)%=P,(Q[pr].mul*=m)%=P;
    ((Q[pl].add*=m)+=a)%=P,((Q[pr].add*=m)+=a)%=P;
    Q[p].add=0;Q[p].mul=1;
}
inline void change1(Re p,Re l,Re r,Re x){
    Re L=Q[p].l,R=Q[p].r;
    if(l<=L&&R<=r){(Q[p].S+=x*(R-L+1))%=P,(Q[p].add+=x)%=P;return;}
    Re mid=L+R>>1;spread(p);
    if(l<=mid)change1(pl,l,r,x);
    if(r>mid)change1(pr,l,r,x);
    Q[p].S=(Q[pl].S+Q[pr].S)%P;
}
inline void change2(Re p,Re l,Re r,Re x){
    Re L=Q[p].l,R=Q[p].r;
    if(l<=L&&R<=r){(Q[p].S*=x)%=P,(Q[p].add*=x)%=P,(Q[p].mul*=x)%=P;return;}
    Re mid=L+R>>1;spread(p);
    if(l<=mid)change2(pl,l,r,x);
    if(r>mid)change2(pr,l,r,x);
    Q[p].S=(Q[pl].S+Q[pr].S)%P;
}
inline LL ask(Re p,Re l,Re r){
    Re L=Q[p].l,R=Q[p].r;
    if(l<=L&&R<=r)return Q[p].S;
    Re mid=L+R>>1,ans=0;spread(p);
    if(l<=mid)(ans+=ask(pl,l,r))%=P;
    if(r>mid)(ans+=ask(pr,l,r))%=P;
    return ans;
}
inline void in(Re &x){
    x=fu=0;char c=getchar();
    while(c<'0'||c>'9')fu|=c=='-',c=getchar();
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
    if(fu)x=-x;
}
int main(){
    in(n),in(m),in(P);
    for(i=1;i<=n;i++)in(a[i]);
    creat(1,1,n);
    while(m--){
        in(b),in(c),in(d);
        if(b>2)printf("%lld\n",ask(1,c,d));
        else{
            in(e);
            if(b>1)change1(1,c,d,e%P);
            else change2(1,c,d,e%P);
        }
    }
}

3.【区间修改(+,/),区间查询(Sum,min)】

「雅礼集训 \(2017\) \(\text{Day1}\)」市场 \(\text{[Loj6029]}\)

#include<cstring>
#include<cstdio>
#include<cmath>
#define LL long long
#define Re register LL
#define pl p<<1
#define pr p<<1|1
#define div(a,b) floor((double)a/b)
const int N=1e5+3;
struct QAQ{LL l,r,ma,mi,S,add;}Q[N<<2];
LL T,i,b,c,d,n,m,fu,a[N];
inline void in(Re &x){
    x=fu=0;char c=getchar();
    while(c<'0'||c>'9')fu|=c=='-',c=getchar();
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
    if(fu)x=-x;
}
inline LL max(Re a,Re b){return a>b?a:b;}
inline LL min(Re a,Re b){return a<b?a:b;}
inline void down(Re p){
    Re adds=Q[p].add;
    if(adds){
        Q[pl].add+=adds,Q[pr].add+=adds;
        Q[pl].ma+=adds,Q[pr].ma+=adds;
        Q[pl].mi+=adds,Q[pr].mi+=adds;
        Q[pl].S+=adds*(Q[pl].r-Q[pl].l+1),Q[pr].S+=adds*(Q[pr].r-Q[pr].l+1);
        Q[p].add=0;
    }
}
inline void up(Re p){
    Q[p].S=Q[pl].S+Q[pr].S;
    Q[p].ma=max(Q[pl].ma,Q[pr].ma);
    Q[p].mi=min(Q[pl].mi,Q[pr].mi);
}
inline void creat(Re p,Re l,Re r){
    Q[p].l=l,Q[p].r=r;
    if(l==r){Q[p].ma=Q[p].mi=Q[p].S=a[l];return;}
    Re mid=l+r>>1;
    creat(pl,l,mid),creat(pr,mid+1,r);
    up(p);
}
inline void plus_(Re p,Re l,Re r,Re x){
    Re L=Q[p].l,R=Q[p].r,mid=L+R>>1;
    if(l<=L&&R<=r){Q[p].S+=x*(R-L+1),Q[p].ma+=x,Q[p].mi+=x,Q[p].add+=x;return;}
    down(p);
    if(l<=mid)plus_(pl,l,r,x);
    if(r>mid)plus_(pr,l,r,x);
    up(p);
}
inline void div_(Re p,Re l,Re r,Re d){
    Re L=Q[p].l,R=Q[p].r,mid=L+R>>1;
    if(l<=L&&R<=r&&(Q[p].ma-div(Q[p].ma,d)==Q[p].mi-div(Q[p].mi,d))){
        Re c=div(Q[p].ma,d)-Q[p].ma;
        Q[p].S+=c*(R-L+1),Q[p].add+=c,Q[p].ma+=c,Q[p].mi+=c;
        return;
    }
    down(p);
    if(l<=mid)div_(pl,l,r,d);
    if(r>mid)div_(pr,l,r,d);
    up(p);
}
inline LL ask_min(Re p,Re l,Re r){
    Re L=Q[p].l,R=Q[p].r;
    if(l<=L&&R<=r)return Q[p].mi;
    Re mid=L+R>>1,ans=2e9;
    down(p);
    if(l<=mid)ans=min(ans,ask_min(pl,l,r));
    if(r>mid)ans=min(ans,ask_min(pr,l,r));
    return ans;
}
inline LL ask_sum(Re p,Re l,Re r){
    Re L=Q[p].l,R=Q[p].r;
    if(l<=L&&R<=r)return Q[p].S;
    Re mid=L+R>>1,ans=0;
    down(p);
    if(l<=mid)ans+=ask_sum(pl,l,r);
    if(r>mid)ans+=ask_sum(pr,l,r);
    return ans;
}
int main(){
    in(n),in(T);
    for(i=1;i<=n;i++)in(a[i]);
    creat(1,1,n);
    while(T--){
        in(b),in(c),in(d),++c,++d;
        if(b<2)in(i),plus_(1,c,d,i);
        else if(b<3)in(i),div_(1,c,d,i);
        else if(b<4)printf("%lld\n",ask_min(1,c,d));
        else printf("%lld\n",ask_sum(1,c,d));
    }
}

4.【区间修改(sqrt),区间查询(Sum)】

\(\text{GSS4 - Can you answer these queries IV} [SP2713]\)

一个数不断地求 \(sqrt\) 会迅速变为 \(1\),此时不需要再继续开方,暴力单修加上剪枝即可。

#include<cstring>
#include<cstdio>
#include<cmath>
#define Re register int
#define pl p<<1
#define pr p<<1|1
const int N=5e4+3;
struct QAQ{int l,r,ma,S;}Q[N<<2];
int i,b,c,d,n,m,fu,a[N];
inline int max(Re a,Re b){return a>b?a:b;}
inline void creat(Re p,Re l,Re r){
    Q[p].l=l,Q[p].r=r;
    if(l==r){Q[p].ma=Q[p].S=a[l];return;}
    Re mid=l+r>>1;
    creat(pl,l,mid),creat(pr,mid+1,r);
    Q[p].S=Q[pl].S+Q[pr].S;
    Q[p].ma=max(Q[pl].ma,Q[pr].ma);
}
inline void change(Re p,Re l,Re r){
    Re L=Q[p].l,R=Q[p].r,mid=L+R>>1;
    if(L==R){Q[p].ma=sqrt(Q[p].ma),Q[p].S=sqrt(Q[p].S);return;}
    if(l<=mid&&Q[pl].ma>1)change(pl,l,r);
    if(r>mid&&Q[pr].ma>1)change(pr,l,r);
    Q[p].S=Q[pl].S+Q[pr].S;
    Q[p].ma=max(Q[pl].ma,Q[pr].ma);
}
inline int ask(Re p,Re l,Re r){
    Re L=Q[p].l,R=Q[p].r;
    if(l<=L&&R<=r)return Q[p].S;
    Re mid=L+R>>1;int ans=0;
    if(l<=mid)ans+=ask(pl,l,r);
    if(r>mid)ans+=ask(pr,l,r);
    return ans;
}
inline void in(Re &x){
    x=fu=0;char c=getchar();
    while(c<'0'||c>'9')fu|=c=='-',c=getchar();
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
    if(fu)x=-x;
}
int main(){
    scanf("%d",&n);
    memset(Q,0,sizeof(Q));
    for(i=1;i<=n;i++)in(a[i]);
    creat(1,1,n);
    while(n--){
        in(b),in(c),in(d),in(i);
        if(b)printf("%d\n",ask(1,c,d));
        else change(1,c,d);
    }
}

5.【区间修改(+),区间查询(最大子段和)】

\(\text{GSS3 - Can you answer these queries III} [SP1716]\)

对每个区间维护最大子序列 \(S\),紧靠左边界的最大子序列 \(ls\),紧靠右边界的最大子序列 \(rs\)

#include<cstdio>
#define Re register int
#define pl p<<1
#define pr p<<1|1
const int N=5e4+3;
struct QAQ{int l,r,S,ls,rs,ans;}Q[N<<2];
int i,b,c,d,n,m,fu,a[N];
inline int max(int a,int b){return a>b?a:b;}
inline void up(QAQ &P,QAQ L,QAQ R){
    P.S=L.S+R.S;
    P.ls=max(L.ls,L.S+R.ls);
    P.rs=max(R.rs,R.S+L.rs);
    P.ans=max(max(L.ans,R.ans),L.rs+R.ls);
}
inline void creat(Re p,Re l,Re r){
    Q[p].l=l,Q[p].r=r;
    if(l==r){Q[p].ans=Q[p].ls=Q[p].rs=Q[p].S=a[l];return;}
    Re mid=l+r>>1;
    creat(pl,l,mid),creat(pr,mid+1,r);
    up(Q[p],Q[pl],Q[pr]);
}
inline void change(Re p,Re w,Re x){
    Re L=Q[p].l,R=Q[p].r,mid=L+R>>1;
    if(L==R){Q[p].ans=Q[p].ls=Q[p].rs=Q[p].S=x;return;}
    if(w<=mid)change(pl,w,x);
    else change(pr,w,x);
    up(Q[p],Q[pl],Q[pr]);
}
inline QAQ ask(Re p,Re l,Re r){
    Re L=Q[p].l,R=Q[p].r,mid=L+R>>1;
    if(l<=L&&R<=r)return Q[p];
    if(r<=mid)return ask(pl,l,r);
    if(l>mid)return ask(pr,l,r);
    QAQ x=ask(pl,l,mid),y=ask(pr,mid+1,r),ans;
    up(ans,x,y);
    return ans;
}
inline void in(int &x){
    x=fu=0;char c=getchar();
    while(c<'0'||c>'9')fu|=c=='-',c=getchar();
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
    if(fu)x=-x;
}
int main(){
    in(n);
    for(i=1;i<=n;i++)in(a[i]);
    creat(1,1,n);in(m);
    while(m--){
        in(b),in(c),in(d);
        if(b)printf("%d\n",ask(1,c,d).ans);
        else change(1,c,d);
    }
}

6.【区间修改(+),区间查询(GCD)】

\(\text{Interval}\) \(\text{GCD}\) \(\text{[CH4302]}\)

\(gcd(x,y)=gcd(x,y-x)\)
\(gcd(x,y,z)=gcd(x,y-x,z-y)\)
\(...\)

线段树维护差分序列 \(c\),树状数组维护原序列 \(a\)\(gcd(a[l],a[l+1]...a[r])=gcd(a[l],gcd(c[l+1]...c[r]))\)

#include<cstdio>
#define LL long long
#define Re register LL
#define pl p<<1
#define pr p<<1|1
const int N=5e5+3;
struct QAQ{LL l,r,gcd;}Q[N<<2];
LL i,c,d,e,n,m,fu,C[N],a[N],b[N];char k;
inline LL abs(Re a){return a<0?-a:a;}
inline LL gcd(Re n,Re m){
    if(!m)return n;
    return n%m?gcd(m,n%m):m;
}
inline void add_c(Re x,Re y){while(x<=n)C[x]+=y,x+=x&(-x);}
inline LL ask_x(Re x){
    Re ans=0;
    while(x)ans+=C[x],x-=x&(-x);
    return ans;
}
inline void creat(Re p,Re l,Re r){
    Q[p].l=l,Q[p].r=r;
    if(l==r){Q[p].gcd=b[l];return;}
    Re mid=l+r>>1;
    creat(pl,l,mid),creat(pr,mid+1,r);
    Q[p].gcd=gcd(Q[pl].gcd,Q[pr].gcd);
}
inline void change(Re p,Re w,Re x){
    Re L=Q[p].l,R=Q[p].r;
    if(L==R){Q[p].gcd+=x;return;}
    Re mid=L+R>>1;
    if(w<=mid)change(pl,w,x);
    else change(pr,w,x);
    Q[p].gcd=gcd(Q[pl].gcd,Q[pr].gcd);
}
inline LL ask(Re p,Re l,Re r){
    Re L=Q[p].l,R=Q[p].r;
    if(l<=L&&R<=r)return abs(Q[p].gcd);
    Re mid=L+R>>1;
    if(r<=mid)return ask(pl,l,r);
    if(l>mid)return ask(pr,l,r);
    return gcd(ask(pl,l,r),ask(pr,l,r));
}
inline void in(Re &x){
    x=fu=0;char c=getchar();
    while(c<'0'||c>'9')fu|=c=='-',c=getchar();
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
    if(fu)x=-x;
}
int main(){
    in(n),in(m);
    for(i=1;i<=n;i++)in(a[i]),b[i]=a[i]-a[i-1],add_c(i,b[i]);
    creat(1,1,n);
    while(m--){
        scanf(" %c",&k),in(c),in(d);
        if(k=='Q')printf("%lld\n",gcd(ask_x(c),ask(1,c+1,d)));
        else{
            in(e),add_c(c,e),add_c(d+1,-e);
            change(1,c,e);
            if(d<n)change(1,d+1,-e);
        }
    }
}

二:【扫描线】

三:【权值线段树区间第 K 小】

#define Re register int
#define pl tree[p].PL
#define pr tree[p].PR
inline int ask(Re p,Re L,Re R,Re k){//查询第k小
    if(L==R)return L;//边界叶节点
    Re tmp=tree[pl].g;//计算左子树(数值范围在L~mid的数)共有多少个数字
    if(tmp>=k)return ask(pl,L,mid,k);
//左子树已经超过k个,说明第k小在左子树里面
    else return ask(pr,mid+1,R,k-tmp);
//左子树不足k个数字,应该在右子树中找到第(k-tmp)小
}

四:【动态开点】

#define Re register int
#define pl tree[p].PL
#define pr tree[p].PR
int cnt;
inline void sakura(Re &p,Re L,Re R,Re ???){//【???修改】
    if(!p)p=++cnt,tree[p].?=???;
//发现进入了一个空节点,新建一个节点,赋予它编号,记录基本信息
    if(L==R){tree[p].?=???;return;}
//达到叶子节点,记录一些特殊的信息,并返回
    Re tmp=???;//可能会在在递归之前进行一些计算来方便判断
    if(???)sakura(pl,L,mid,???);//递归进入左子树
    if(???)sakura(pr,mid+1,R,???);//递归进入右子树
    tree[p].?=???;//回溯后更新信息
}

五:【线段树合并】

#define Re register int
#define pl tree[p].PL
#define pr tree[p].PR
inline int merge(Re p,Re q){//【线段树合并】
    if(!p)return q;if(!q)return p;
    //当需要合并的点的其中一个编号为0时 (即为空),返回另一个编号
    tr[p].g+=tr[q].g,p;//把q合并到p上面去
    pl=merge(pl,tr[q].lp);//合并左子树,并记录p点的左子树编号
    pr=merge(pr,tr[q].rp);//合并右子树,并记录p点的右子树编号
    return p;
}

六:【可持续化线段树—静态主席树】

【模板】可持久化线段树 \(1\) (主席树) \(\text{[3834]}\) / \(\text{K-th Number [POJ2104]}\) \(\text{[SP3946]}\)

#include<algorithm>
#include<cstdio>
#define mid (L+R>>1)
#define pl tr[p].lp
#define pr tr[p].rp
#define Re register int
#define F(a,b) for(i=a;i<=b;++i)
using namespace std;
const int N=1e5+3;
int x,y,z,i,n,m,k,t,fu,cnt,a[N],b[N],pt[N];//pt[i]表示离散化后i这个位置所对应的权值树根的编号
struct QAQ{int g,lp,rp;}tr[N<<5];//权值树,保守开一个32*N
inline void in(Re &x){//【快读】自己动手,丰衣足食...
    x=fu=0;char c=getchar();
    while(c<'0'||c>'9')fu|=c=='-',c=getchar();
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();x=fu?-x:x;
}
inline int creat(Re pp,Re x,Re L,Re R){
//把上一棵权值树pp(即pt[a[i-1]])复制过来
//并在递归复制途中对x(即a[i]离散化后的位置)这个点同步进行单修操作
    Re p=++cnt;pl=tr[pp].lp,pr=tr[pp].rp,tr[p].g=tr[pp].g+1;
    //新开一个点,并把上一个的数据复制进来,并使tr[].g++
    if(L==R)return p;//到达边界: L==R(即x这个位置)
    if(x<=mid)pl=creat(tr[pp].lp,x,L,mid);//递归进入条件:单修
    else pr=creat(tr[pp].rp,x,mid+1,R);//注意tr[pp]要同时递归至左(右)子树
    return p;
}
inline int ask(Re p,Re pp,Re L,Re R,Re k){
//查询。p为查询区间左端点的权值树根编号,pp为查询区间右端点的权值树根编号
    if(L==R)return b[R];//边界:L==R
    Re tmp=tr[tr[pp].lp].g-tr[pl].g;//用前缀和思想计算出左子树共有多少个数字
    if(tmp>=k)return ask(pl,tr[pp].lp,L,mid,k);//左子树已经超过k个,说明第k小在左子树里面
    else return ask(pr,tr[pp].rp,mid+1,R,k-tmp);//左子树不足k个,应该在右子树中找第(k-tmp)小
}
int main(){
    in(n),in(k);
    F(1,n)in(a[i]),b[i]=a[i];//复制进b[]并离散去重
    sort(b+1,b+n+1);//【离散化】
    m=unique(b+1,b+n+1)-b-1;//【去重】
    F(1,n)pt[i]=creat(pt[i-1],lower_bound(b+1,b+m+1,a[i])-b,1,m);
    //找出当前这个位置按权值排序后的位置x,进入建树
    while(k--)in(x),in(y),in(z),printf("%d\n",ask(pt[x-1],pt[y],1,m,z));//注意是【y】-【x-1】
}

七:【可持续化线段树—动态主席树】

\(\text{Dynamic Rankings [P2617]}\) \(\text{[ZOJ2112]}\) \(\text{[BZOJ1901]}\)

#include<algorithm>
#include<cstring>
#include<cstdio>
#define mid (L+R>>1)
#define Re register int
#define F(i,a,b) for(Re i=a;i<=b;++i)
using namespace std;
const int N=2e5+3;char opt[N];
int x,y,z,n,m,T,t,fu,cnt,tl,tr,a[N],b[N],pt[N],C[N],ptl[20],ptr[20];
//ptl,ptr千万不要开N,否则memset的时候会TLE到怀疑人生
struct QAQ{int g,lp,rp;}tree[N*400];//本应是441左右,开小一点也无所谓,因为根本用不到
struct O_O{int l,r,k;}Q[N];//储存Q次查询的内容,方便离散化
struct T_T{int i,x;}c[N];//离散化数组
inline void in(int &x){
    x=fu=0;char c=getchar();
    while(c<'0'||c>'9')fu|=c=='-',c=getchar();
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
    x=fu?-x:x;
}
inline int ask_(Re L,Re R,Re k){
    if(L==R)return b[R];//注意:返回的值需要用到的是哪一个映射数组不能搞错
    Re tmp=0;
    F(i,1,tl)tmp-=tree[tree[ptl[i]].lp].g;//计算左子树信息
    F(i,1,tr)tmp+=tree[tree[ptr[i]].lp].g;//计算左子树信息
    if(tmp>=k){
        F(i,1,tl)ptl[i]=tree[ptl[i]].lp;//更新ptl,ptr所指向的节点编号
        F(i,1,tr)ptr[i]=tree[ptr[i]].lp;
        return ask_(L,mid,k);
    }
    else{
        F(i,1,tl)ptl[i]=tree[ptl[i]].rp;
        F(i,1,tr)ptr[i]=tree[ptr[i]].rp;
        return ask_(mid+1,R,k-tmp);
    }
}
inline int ask(Re L,Re R,Re k){//查询第k小
    memset(ptl,0,sizeof(ptl));//万恶的memset
    memset(ptr,0,sizeof(ptr));//数组开太大会疯狂抢时间复杂度
    tl=tr=0;
    for(Re i=L-1;i;i-=i&-i)ptl[++tl]=pt[i];//先把所有要更新的位置的线段树根节点记录下来
    for(Re i=R;i;i-=i&-i)ptr[++tr]=pt[i];//方便后面递归更新信息
    return ask_(1,m,k);
}
inline void change(Re &p,Re L,Re R,Re w,Re v){
    if(!p)p=++cnt;tree[p].g+=v;
    if(L==R)return;
    if(w<=mid)change(tree[p].lp,L,mid,w,v);
    else change(tree[p].rp,mid+1,R,w,v);
}
inline void add(Re x,Re v){//【单点修改】
    Re w=lower_bound(b+1,b+m+1,a[x])-b;//注意函数传进来的参数和这里各种映射数组的调用不要搞错
    for(Re i=x;i<=n;i+=i&-i)change(pt[i],1,m,w,v);//树状数组思想更新信息
}
int main(){
//  printf("%lf\n",(sizeof(tree))/1024.0/1024.0);
//  printf("%lf\n",(sizeof(tree)+sizeof(Q)+sizeof(c)+sizeof(a)+sizeof(b)+sizeof(pt)+sizeof(C))/1024.0/1024.0);
    in(n),in(T),m=n;
    F(i,1,n)in(a[i]),b[i]=a[i];
    F(i,1,T){
        scanf(" %c",&opt[i]);
        if(opt[i]=='Q')in(Q[i].l),in(Q[i].r),in(Q[i].k);
        else in(c[i].i),in(c[i].x),b[++m]=c[i].x;
    }
    sort(b+1,b+m+1);
    m=unique(b+1,b+m+1)-b-1;
    F(i,1,n)add(i,1);//初始化建树
    F(i,1,T){
        if(opt[i]=='Q')printf("%d\n",ask(Q[i].l,Q[i].r,Q[i].k));
        else add(c[i].i,-1),a[c[i].i]=c[i].x,add(c[i].i,1);
//先让这个位置上原来的数减少一个,再把新数加一个,就达到了替换的目的
    }
}

八:【树套树扩展(树状数组套线段树维护动态平衡树)】

好长的标题啊QAQ。

众所周知,万能的线段树啥都能维护

#include<algorithm>
#include<cstring>
#include<cstdio>
#define mid (L+R>>1)
#define Re register int
#define pl tree[p].lp
#define pr tree[p].rp
#define F(i,a,b) for(Re i=a;i<=b;++i)
#define lo(o) lower_bound(b+1,b+m+1,o)-b
using namespace std;
const int N=1e5+3,inf=2147483647;//【N不乘2 WA上天】由于要离散化,加上查询最多n+m(即2*n)个数据
int x,y,z,n,m,T,t,fu,cnt,tl,tr,a[N],b[N],pt[N],C[N],opt[N],ptl[20],ptr[20];
struct QAQ{int g,lp,rp;}tree[N*250];//本应是17*17=289左右,开小一点也无所谓,因为根本用不到
struct O_O{int l,r,k;}Q[N];//储存Q次查询的具体内容,方便离散化
struct T_T{int i,x;}c[N];//单点修改的具体内容
inline void in(Re &x){
    x=fu=0;char c=getchar();
    while(c<'0'||c>'9')fu|=c=='-',c=getchar();
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
    x=fu?-x:x;
}
inline int ask_kth(Re L,Re R,Re k){//查询第k小
    if(L==R)return b[R];//【映射混用 WA上天】注意:返回的值需要用到的是哪一个映射数组不能搞错
    Re tmp=0;
    F(i,1,tl)tmp-=tree[tree[ptl[i]].lp].g;//计算左子树信息
    F(i,1,tr)tmp+=tree[tree[ptr[i]].lp].g;//计算左子树信息
    if(tmp>=k){
        F(i,1,tl)ptl[i]=tree[ptl[i]].lp;//更新ptl,ptr所指向的节点编号
        F(i,1,tr)ptr[i]=tree[ptr[i]].lp;
        return ask_kth(L,mid,k);
    }
    else{
        F(i,1,tl)ptl[i]=tree[ptl[i]].rp;
        F(i,1,tr)ptr[i]=tree[ptr[i]].rp;
        return ask_kth(mid+1,R,k-tmp);
    }
}
inline int ask_kth_pre(Re L,Re R,Re k){//查询第k小(中转站)
    tl=tr=0;//(注意L-1)
    for(Re i=L-1;i;i-=i&-i)ptl[++tl]=pt[i];//先把所有要更新的位置的线段树根节点记录下来
    for(Re i=R;i;i-=i&-i)ptr[++tr]=pt[i];//方便后面递归更新信息
    return ask_kth(1,m,k);
}
inline void add(Re &p,Re L,Re R,Re w,Re v){//【单点修改】
    if(!p)p=++cnt;tree[p].g+=v;
    if(L==R)return;
    if(w<=mid)add(pl,L,mid,w,v);
    else add(pr,mid+1,R,w,v);
}
inline void add_pre(Re x,Re v){//【单点修改】
    Re w=lo(a[x]);//【映射混用 TLE上天】注意函数传进来的参数x是在原数列的位置c[i].i(方便更新原数列),这里各种映射数组的调用不要搞错
    for(Re i=x;i<=n;i+=i&-i)add(pt[i],1,m,w,v);//树状数组思想更新信息
}
inline int ask_level(Re p,Re L,Re R,Re x){//查询小于等于x的数的个数
    if(L==R)return tree[p].g;
    if(x<=mid)return ask_level(pl,L,mid,x);
    else return tree[pl].g+ask_level(pr,mid+1,R,x);
}
inline int ask_level_pre(Re L,Re R,Re w){//查询x的排名(中转站)
    Re ans=0;
    for(Re i=R;i;i-=i&-i)ans+=ask_level(pt[i],1,m,w);
    for(Re i=L-1;i;i-=i&-i)ans-=ask_level(pt[i],1,m,w);
    return ans;
}
int main(){
//  printf("%lf\n",(sizeof(tree))/1024.0/1024.0);
//  printf("%lf\n",(sizeof(tree)+sizeof(Q)+sizeof(c)+sizeof(a)+sizeof(b)+sizeof(pt)+sizeof(C))/1024.0/1024.0);
    in(n),in(T),m=n;
    F(i,1,n)in(a[i]),b[i]=a[i];
    F(i,1,T){
        in(opt[i]);
        if(opt[i]==3)in(c[i].i),in(c[i].x),b[++m]=c[i].x;
        else{
            in(Q[i].l),in(Q[i].r),in(Q[i].k);
            if(opt[i]!=2)b[++m]=Q[i].k;//【不离散 WA上天】除了2的查询不用管,其他地方出现的k全部都要离散化
        }
    }
    sort(b+1,b+m+1);
    m=unique(b+1,b+m+1)-b-1;//unique()是-(b+1),lower_bound()是-b
    F(i,1,n)add_pre(i,1);//初始化建树
    F(i,1,T){
        if(opt[i]==1)//查询x的排名(中转站)
            Q[i].k=lo(Q[i].k),//【直接查询 WA上天】先查询Q[i].k在b中的的位置,将其减一查得 ≤他前一个数 的总个数
            printf("%d\n",ask_level_pre(Q[i].l,Q[i].r,Q[i].k-1)+1);//再加一查得Q[i].k的排名,酱紫可以有效避过Q[i].k的副本处理

        if(opt[i]==2)
            printf("%d\n",ask_kth_pre(Q[i].l,Q[i].r,Q[i].k));//查询第k小(中转站)

        if(opt[i]==3)//修改某一位值上的数值(中转站)
            add_pre(c[i].i,-1),a[c[i].i]=c[i].x,add_pre(c[i].i,1);
        //先让这个位置上原来的数减少一个,更新数字后再把新数加一个,就达到了替换的目的

        if(opt[i]==4){//查询前驱(严格小于)
/*1>取位置*/Q[i].k=lo(Q[i].k);//【直接查询 WA上天】先查询Q[i].k在b中的位置,将其位置减一查询得前驱
/*2>找排名*/Re level=ask_level_pre(Q[i].l,Q[i].r,Q[i].k-1);//因为在离散化数组中是找不到Q[i].k-1这个数字的,所以不能直接查询具体数值
/*3>判有无*/if(!level)printf("%d\n",-inf);//【判断条件错误 WA到上天】由于这里level是取出的前驱在b中的位置,所以只要【level>0】就可以啦
           //(如果你按着上面【直接查询 WA上天】的注释改了代码,却没有改这里的【条件判断】,那么你的level<=1将会让你【WA上天】)。
/*4>找结果*/else printf("%d\n",ask_kth_pre(Q[i].l,Q[i].r,level));
        }

        if(opt[i]==5){//查询猴急(严格大于)【盲目复制 WA上天】如果你采用了同上的方法,等着死翘翘吧
/*1>取位置*/Q[i].k=lo(Q[i].k);
/*2>找排名*/Re level=ask_level_pre(Q[i].l,Q[i].r,Q[i].k);//【直接查询 WA上天】如果同上,会越界,上面的越界是b[0]=0所以不慌,嘿嘿,而这里b[n+1]=0就不行了哟
/*3>判有无*/if(level==Q[i].r-Q[i].l+1)printf("%d\n",inf);//【判断条件错误 WA上天】这里猴急应是level+1,所以条件应是【level≤区间总长度】
/*4>找结果*/else printf("%d\n",ask_kth_pre(Q[i].l,Q[i].r,level+1));//【盲目复制 WA上天】 别忘了加一,和前驱不同啦!
        }
    }
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!