线段树训练

线段树

什么是线段树?

线段树是一种二叉搜索树,而二叉搜索树,首先满足二叉树,即每个结点最多有两颗子树,并且是一颗搜索树,我们要知道,线段树的每个结点都存储了一个区间,也可以理解成一个线段,而搜索,就是在这些线段上进行搜索操作得到你想要的答案。(处理区间问题

只能维护带有结合律的信息:(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;
}
  • 8
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值