【模板整合计划】高阶数据结构—线段树
一:【基本操作及扩展】
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上天】 别忘了加一,和前驱不同啦! } } }