线段树
什么是线段树?
线段树是一种二叉搜索树,而二叉搜索树,首先满足二叉树,即每个结点最多有两颗子树,并且是一颗搜索树,我们要知道,线段树的每个结点都存储了一个区间,也可以理解成一个线段,而搜索,就是在这些线段上进行搜索操作得到你想要的答案。(处理区间问题)
只能维护带有结合律的信息:(1)总数字之和=左区间数字之和+右区间数字之和(2)总gcd=gcd(左区间gcd,右区间gcd)(3)总最大值=max(左区间最大值,右区间最大值)。
单点修改,区间查询
洛谷p1531黄
题意;有n个同学m个操作,第二行n个正数,第i个数代表第i个同学的成绩。操作有两种:询问区间内最大的数;小于b的成绩更新为b。
代码:
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=5e5+10; ll a[N]; struct A{ int l,r;//区间 ll lazy;//懒标记 ll sum;//维护 }tr[N<<2]; void pushup(int u){//得到两个子区间的区间和 tr[u].sum=max(tr[u<<1].sum,tr[u<<1|1].sum); } void build(int u,int l,int r){//建树 tr[u].l=l; tr[u].r=r; if(l==r){ tr[u].sum=a[l]; return; } int mid=l+r>>1; build(u<<1,l,mid); build(u<<1|1,mid+1,r); pushup(u);//从子节点向父节点的信息整合 } void update(int u,int pos,int k){//区间更新 if(tr[u].l==tr[u].r){ if(tr[u].sum<k){ tr[u].sum=k; } return; } int mid=(tr[u].l+tr[u].r)>>1; if(pos<=mid)update(u<<1,pos,k); else update(u<<1|1,pos,k); pushup(u); } ll query(int u,int l,int r){//区间查询 if(tr[u].l>=l&&tr[u].r<=r)return tr[u].sum; int mid=(tr[u].l+tr[u].r)>>1; ll ma=0; if(l<=mid)ma=max(ma,query(u<<1,l,r)); if(r>mid)ma=max(ma,query(u<<1|1,l,r)); return ma; } int main(){ int n,m;cin>>n>>m; for(int i=1;i<=n;i++)cin>>a[i]; build(1,1,n); for(int i=1;i<=m;i++){ char t;cin>>t; if(t=='U'){ ll x,y;cin>>x>>y; update(1,x,y); } else{ ll x,y;cin>>x>>y; cout<<query(1,x,y)<<endl; } } return 0; }
洛谷p3374黄
1 x k:将第x个数加上k
2 x y:输出区间x,y内每个数的和。
代码:
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=5e5+10; ll a[N]; struct A{ int l,r;//区间 ll lazy;//懒标记 ll sum;//维护 }tr[N<<2]; void pushup(int u){//得到两个子区间的区间和 tr[u].sum=tr[u<<1].sum+tr[u<<1|1].sum; } void build(int u,int l,int r){//建树 tr[u].l=l; tr[u].r=r; if(l==r){ tr[u].sum=a[l]; return; } int mid=l+r>>1; build(u<<1,l,mid); build(u<<1|1,mid+1,r); pushup(u);//从子节点向父节点的信息整合 } void update(int u,int pos,int k){//区间更新 if(tr[u].l==tr[u].r){ tr[u].sum+=k; return; } int mid=(tr[u].l+tr[u].r)>>1; if(pos<=mid)update(u<<1,pos,k); else update(u<<1|1,pos,k); pushup(u); } ll query(int u,int l,int r){//区间查询 if(tr[u].l>=l&&tr[u].r<=r)return tr[u].sum; int mid=(tr[u].l+tr[u].r)>>1; ll ma=0; if(l<=mid)ma+=query(u<<1,l,r); if(r>mid)ma+=query(u<<1|1,l,r); return ma; } int main(){ int n,m;cin>>n>>m; for(int i=1;i<=n;i++)cin>>a[i]; build(1,1,n); for(int i=1;i<=m;i++){ int t;cin>>t; if(t==1){ ll x,y;cin>>x>>y; update(1,x,y); } else{ ll x,y;cin>>x>>y; cout<<query(1,x,y)<<endl; } } return 0; }
区间修改,单点查询
洛谷p3368黄
1 x y k :将区间x,y内每个数加上k
2 x输出第x个数的值
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=5e5+10; ll a[N]; struct A{ int l,r;//区间 ll lazy;//懒标记 ll sum;//维护 }tr[N<<2]; void pushup(int u){//得到两个子区间的区间和 tr[u].sum=min(tr[u<<1].sum,tr[u<<1|1].sum); } void build(int u,int l,int r){//建树 tr[u].l=l; tr[u].r=r; if(l==r){ tr[u].sum=a[l]; return; } int mid=l+r>>1; build(u<<1,l,mid); build(u<<1|1,mid+1,r); pushup(u);//从子节点向父节点的信息整合 } void pushdown(int u){//懒标记的传递 if(tr[u].lazy){ tr[u<<1].lazy+=tr[u].lazy; tr[u<<1].sum+=(tr[u<<1].r-tr[u<<1].l+1)*tr[u].lazy; tr[u<<1|1].lazy+=tr[u].lazy; tr[u<<1|1].sum+=(tr[u<<1|1].r-tr[u<<1|1].l+1)*tr[u].lazy; tr[u].lazy=0; } } void update(int u,int l,int r,int k){//区间更新 if(l<=tr[u].l&&r>=tr[u].r){ tr[u].sum+=(tr[u].r-tr[u].l+1)*k; tr[u].lazy+=k; return; } pushdown(u); int mid=tr[u].l+tr[u].r>>1; if(l<=mid)update(u<<1,l,r,k); if(r>mid)update(u<<1|1,l,r,k); pushup(u); } ll query(int u,int pos,int l,int r){//单点查询 if(l==r)return tr[u].sum; pushdown(u); int mid=tr[u].l+tr[u].r>>1; if(pos<=mid)return query(u<<1,pos,l,mid); else if(pos>mid)return query(u<<1|1,pos,mid+1,r); } int main(){ int n,m;cin>>n>>m; for(int i=1;i<=n;i++)cin>>a[i]; build(1,1,n); for(int i=1;i<=m;i++){ int t;cin>>t; if(t==1){ int l,r;ll k;cin>>l>>r>>k; update(1,l,r,k); } else{ int x;cin>>x; cout<<query(1,x,1,n)<<endl; } } }
区间加法
洛谷p3372绿
1 x y k:将区间x,y内每个数加上k
2 x y:输出区间x,y内每个数的和
代码:
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=1e5+10; int a[N]; struct A{ int l,r;//区间 ll lazy;//懒标记 ll sum;//维护 }tr[N<<2]; void pushup(int u){//得到两个子区间的区间和 tr[u].sum=tr[u<<1].sum+tr[u<<1|1].sum; } void build(int u,int l,int r){//建树 tr[u].l=l; tr[u].r=r; if(l==r){ tr[u].sum=a[l]; return; } int mid=l+r>>1; build(u<<1,l,mid); build(u<<1|1,mid+1,r); pushup(u);//从子节点向父节点的信息整合 } void pushdown(int u){//懒标记的传递 if(tr[u].lazy){ tr[u<<1].lazy+=tr[u].lazy; tr[u<<1].sum+=(tr[u<<1].r-tr[u<<1].l+1)*tr[u].lazy; tr[u<<1|1].lazy+=tr[u].lazy; tr[u<<1|1].sum+=(tr[u<<1|1].r-tr[u<<1|1].l+1)*tr[u].lazy; tr[u].lazy=0; } } void update(int u,int l,int r,int k){//区间更新 if(l<=tr[u].l&&r>=tr[u].r){ tr[u].lazy+=k; tr[u].sum+=(tr[u].r-tr[u].l+1)*k; return; } pushdown(u); int mid=tr[u].l+tr[u].r>>1; if(l<=mid)update(u<<1,l,r,k); if(r>mid)update(u<<1|1,l,r,k); pushup(u); } ll query(int u,int l,int r){//区间查询 if(l<=tr[u].l&&r>=tr[u].r)return tr[u].sum; pushdown(u); int mid=tr[u].l+tr[u].r>>1; ll res=0; if(l<=mid)res+=query(u<<1,l,r); if(r>mid)res+=query(u<<1|1,l,r); return res; } int main(){ int n,m;cin>>n>>m; for(int i=1;i<=n;i++)cin>>a[i]; build(1,1,n); for(int i=1;i<=m;i++){ int t;cin>>t; if(t==1){ int l,r;ll k;cin>>l>>r>>k; update(1,l,r,k); } else{ int l,r;cin>>l>>r; cout<<query(1,l,r)<<endl; } } }
区间乘法
洛谷p3373绿
1 x y k将区间内每个数乘上k
2 x y k将区间内每个数加上k
3 x y输出区间x,y内每个数的和并取模
代码:
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=1e5+10; int a[N],p; struct A{ int l,r;//区间 ll add;//懒标记 ll mul; ll sum;//维护 }tr[N<<2]; void pushup(int u){//得到两个子区间的区间和 tr[u].sum=(tr[u<<1].sum+tr[u<<1|1].sum)%p; } void build(int u,int l,int r){//建树 tr[u].l=l; tr[u].r=r; tr[u].add=0; tr[u].mul=1; if(l==r){ tr[u].sum=a[l]; return; } int mid=l+r>>1; build(u<<1,l,mid); build(u<<1|1,mid+1,r); pushup(u);//从子节点向父节点的信息整合 } void pushdown(int u){//懒标记的传递 if(tr[u].mul!=1){ tr[u<<1].mul=(tr[u<<1].mul*tr[u].mul)%p; tr[u<<1].add=(tr[u<<1].add*tr[u].mul)%p; tr[u<<1].sum=(tr[u<<1].sum*tr[u].mul)%p; tr[u<<1|1].mul=(tr[u<<1|1].mul*tr[u].mul)%p; tr[u<<1|1].add=(tr[u<<1|1].add*tr[u].mul)%p; tr[u<<1|1].sum=(tr[u<<1|1].sum*tr[u].mul)%p; tr[u].mul=1; } if(tr[u].add){ tr[u<<1].add=(tr[u<<1].add+tr[u].add)%p; tr[u<<1].sum=(tr[u<<1].sum+tr[u].add*(tr[u<<1].r-tr[u<<1].l+1)%p)%p; tr[u<<1|1].add=(tr[u<<1|1].add+tr[u].add)%p; tr[u<<1|1].sum=(tr[u<<1|1].sum+tr[u].add*(tr[u<<1|1].r-tr[u<<1|1].l+1)%p)%p; tr[u].add=0; } } void modify(int u,int l,int r,ll v1,ll v2){ if(l<=tr[u].l&&r>=tr[u].r){ tr[u].mul=(tr[u].mul*v1)%p; tr[u].add=(tr[u].add*v1)%p; tr[u].sum=(tr[u].sum*v1)%p; tr[u].add=(tr[u].add+v2)%p; tr[u].sum=(tr[u].sum+v2*(tr[u].r-tr[u].l+1))%p; return; } pushdown(u); int mid=(tr[u].l+tr[u].r)>>1; if(l<=mid)modify(u<<1,l,r,v1,v2); if(r>mid)modify(u<<1|1,l,r,v1,v2); pushup(u); } ll query(int u,int l,int r){//区间查询 if(l<=tr[u].l&&r>=tr[u].r){ return tr[u].sum; } pushdown(u); int mid=(tr[u].l+tr[u].r)>>1; ll res=0; if(l<=mid)res=query(u<<1,l,r); if(r>mid)res=(res+query(u<<1|1,l,r))%p; return res; } int main(){ int n,m;cin>>n>>m>>p; for(int i=1;i<=n;i++)cin>>a[i]; build(1,1,n); for(int i=1;i<=m;i++){ int t;cin>>t; if(t==1){ int l,r;ll k;cin>>l>>r>>k; modify(1,l,r,k,0); } else if(t==2){ int l,r;ll k;cin>>l>>r>>k; modify(1,l,r,1,k); } else{ int l,r;cin>>l>>r; cout<<query(1,l,r)<<endl; } } return 0; }
区间根号
洛谷p4145蓝
0 l r:区间l,r每个数开平方
1 l r:输出区间l,r内每个数的和
代码:
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=1e6+10; ll a[N]; struct A{ ll l,r;//区间 ll add;//懒标记 ll maxn; ll sum;//维护 }tr[N<<2]; void pushup(ll u){//得到两个子区间的区间和 tr[u].sum=tr[u<<1].sum+tr[u<<1|1].sum; tr[u].maxn=max(tr[u<<1].maxn,tr[u<<1|1].maxn); } void build(ll u,ll l,ll r){//建树 tr[u].l=l; tr[u].r=r; if(l==r){ tr[u].sum=tr[u].maxn=a[l]; return; } ll mid=l+r>>1; build(u<<1,l,mid); build(u<<1|1,mid+1,r); pushup(u);//从子节点向父节点的信息整合 } void modify(ll u,ll l,ll r){ if(tr[u].l==tr[u].r){ if(tr[u].maxn>1){ tr[u].sum=sqrt(tr[u].sum); tr[u].maxn=sqrt(tr[u].maxn); } return; } ll mid=(tr[u].l+tr[u].r)>>1; if(l<=mid&&tr[u<<1].maxn>1)modify(u<<1,l,r); if(r>mid&&tr[u<<1|1].maxn>1)modify(u<<1|1,l,r); pushup(u); } ll query(ll u,ll l,ll r){//区间查询 if(l<=tr[u].l&&r>=tr[u].r){ return tr[u].sum; } ll mid=(tr[u].l+tr[u].r)>>1; ll res=0; if(l<=mid)res+=query(u<<1,l,r); if(r>mid)res+=query(u<<1|1,l,r); return res; } int main(){ int n;scanf("%d",&n); for(int i=1;i<=n;i++)scanf("%lld",&a[i]); int m;scanf("%d",&m); build(1,1,n); for(int i=1;i<=m;i++){ int t,l,r; scanf("%d%d%d",&t,&l,&r); if(t==0){ if(l>r)swap(l,r); modify(1,l,r); } else{ if(l>r)swap(l,r); cout<<query(1,l,r)<<endl; } } return 0; }
例题
例1 cf438d(蓝)
题意:有长度为n的数组a,执行m个操作:
1 l r:输出区间内每个数的和
2 l r x:区间内每个数取模,模为x
3 k x: ak修改为x
代码:
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=1e5+10; int a[N]; struct A{ int l,r;//区间 ll maxn;//懒标记 ll sum;//维护 }tr[N<<2]; void pushup(int u){//得到两个子区间的区间和 tr[u].sum=tr[u<<1].sum+tr[u<<1|1].sum; tr[u].maxn=max(tr[u<<1].maxn,tr[u<<1|1].maxn); } void build(int u,int l,int r){//建树 tr[u].l=l; tr[u].r=r; if(l==r){ tr[u].sum=a[l]; tr[u].maxn=a[l]; return; } int mid=l+r>>1; build(u<<1,l,mid); build(u<<1|1,mid+1,r); pushup(u);//从子节点向父节点的信息整合 } void modify1(int u,int l,int r,int x){ if(l<=tr[u].l&&r>=tr[u].r){ if(tr[u].maxn<x)return; } if(tr[u].l==tr[u].r){ tr[u].maxn=tr[u].maxn%x; tr[u].sum=tr[u].sum%x; return; } int mid=tr[u].l+tr[u].r>>1; if(l<=mid)modify1(u<<1,l,r,x); if(r>mid)modify1(u<<1|1,l,r,x); pushup(u); } void modify2(int u,int k,int v){ if(tr[u].l==tr[u].r){ if(tr[u].l==k){ tr[u].maxn=v; tr[u].sum=v; } return; } int mid=tr[u].l+tr[u].r>>1; if(k<=mid)modify2(u<<1,k,v); else modify2(u<<1|1,k,v); pushup(u); } ll query(int u,int l,int r){//区间查询 if(l<=tr[u].l&&r>=tr[u].r)return tr[u].sum; int mid=tr[u].l+tr[u].r>>1; ll res=0; if(l<=mid)res+=query(u<<1,l,r); if(r>mid)res+=query(u<<1|1,l,r); return res; } int main(){ int n,m;cin>>n>>m; for(int i=1;i<=n;i++)cin>>a[i]; build(1,1,n); for(int i=1;i<=m;i++){ int t;cin>>t; if(t==1){ int l,r;cin>>l>>r; cout<<query(1,l,r)<<endl; } else if(t==2){ int l,r;cin>>l>>r; ll x;cin>>x; modify1(1,l,r,x); } else{ int k;ll x;cin>>k>>x; modify2(1,k,x); } } }
例2 p5142(蓝)
1 x y:将bx赋值为y
2 x y:输出bx到by的方差
分析:
∑(a[i]+x)²
=a1²+a2²+。。an²+2x(a1+a2+。。an)+nx²
=sum2[n]+2xsum1[n]+nx²=∑xi²/n-平均数²
代码:
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=1e5+10; int a[N],p; const ll mod=1e9+7; int qpow(int b, int p = mod - 2, int m = mod) { // 快速幂用于费马小定理求逆元 b %= m; int s = 1 % m; for(; p; p >>= 1, b = (ll)b * b % m) if(p & 1) s = (ll)s * b % m; return s; } struct A{ ll l,r;//区间 ll sum;//维护 }tr[N<<2]; struct B{ ll l,r; ll sum;//维护 }tr2[N<<2]; void pushup(int u){//得到两个子区间的区间和 tr[u].sum=(tr[u<<1].sum+tr[u<<1|1].sum)%mod; tr2[u].sum=(tr2[u<<1].sum+tr2[u<<1|1].sum)%mod; } void build(ll u,ll l,ll r){//建树 tr[u].l=l; tr[u].r=r; tr2[u].l=l; tr2[u].r=r; if(l==r){ tr[u].sum=a[l]%mod; tr2[u].sum=((ll)a[l]*a[l])%mod; return; } ll mid=l+r>>1; build(u<<1,l,mid); build(u<<1|1,mid+1,r); pushup(u);//从子节点向父节点的信息整合 } ll query1(int u,int l,int r) { if(l==tr[u].l&&r==tr[u].r)return tr[u].sum%mod; int m=(tr[u].l+tr[u].r)>>1; if(r<=m)return query1(u<<1,l,r) % mod; if(l>m)return query1(u<<1|1,l,r) % mod; return(query1(u<<1,l,m)+query1(u<<1|1,m+1,r))%mod; } ll query2(int u,int l,int r) { if(l==tr2[u].l&&r==tr2[u].r)return tr2[u].sum%mod; int m=(tr2[u].l+tr2[u].r)>>1; if(r<=m)return query2(u<<1,l,r) % mod; if(l>m)return query2(u<<1|1,l,r) % mod; return(query2(u<<1,l,m)+query2(u<<1|1,m+1,r))%mod; } void update1(ll u,ll pos,ll k){//区间更新 if(tr[u].l==tr[u].r){ tr[u].sum=(ll)k%mod; tr2[u].sum=((ll)k*k%mod)%mod; return; } ll mid=(tr[u].l+tr[u].r)>>1; if(pos<=mid)update1(u<<1,pos,k); else update1(u<<1|1,pos,k); pushup(u); } int main(){ ll n,m;cin>>n>>m; for(int i=1;i<=n;i++)cin>>a[i]; build(1,1,n); for(int i=1;i<=m;i++){ ll t;cin>>t;ll x,y;cin>>x>>y; if(t==1){ update1(1,x,y%mod); } else{ ll s1=query1(1,x,y)%mod;//区间和 ll s2=query2(1,x,y)%mod;//区间平方和 ll len=qpow(y-x+1);//区间长度的倒数 ll ave=(ll)s1*len%mod;//平均数 ll ans=(ll)s2*len%mod-((ll)ave*ave%mod)%mod; ans=(ans%mod+mod)%mod;//防止出现负数 cout<<ans<<endl; } } return 0; }