【笔记+模版】 线段树

线段树(Segment tree)是一种二叉树形数据结构,1977年由Jon Louis Bentley发明,用以储存区间或线段,并且允许快速查询结构内包含某一点的所有区间。
一个包含 n个区间的线段树,空间复杂度为 O(nlog n),查询的时间复杂度则为 O(log n+k),其中 k是符合条件的区间数量。(引自维基百科)

注意

1.线段树是一棵满二叉树;
第i层有2^(i-1)个节点;
前i层 共有2^i -1个节点;
一般要开四倍空间(具体证明不太明白);
对于节点m,m<<1(m*2)是它的左儿子,m<<1|1(m*2+1)是它的右儿子
2.线段树的查询和修改都是O(log n)的;
3.使用线段树的操作,必须满足区间可加性

操作

1。建树
结构体:维护l,r,题目要求维护的值(sum,max,min等)以sum为例

struct seg_tree{
    int l,r;
    long long sum;//要维护的值
    int ax;//标记
}t[N<<2];
void updata(int m){//回溯数据更改
    t[m].sum=t[m<<1].sum+t[m<<1|1].sum;
    return ;
}
void build(int m,int ll,int rr){//建树
    t[m].l=ll,t[m].r=rr;
    if(ll==rr){
        t[m].sum=num[ll];
        return ;
    }
    int mid=ll+rr>>1;
    build(m<<1,ll,mid);//递归处理左儿子
    build(m<<1|1,mid+1,rr);//右儿子
    updata(m);//回溯数据收集
    return ;
}

2。修改
单点修改

void change(int m,int wz,int w){
    if(t[m].l==t[m].r){//找到目标节点
        t[m].sum+=w;//更改目标节点值
        return ;
    }
    int mid=t[m].l+t[m].r>>1;
    if(wz<=mid) change(m<<1,wz,w);//目标在左儿字部
    else change(m<<1|1,wz,w);//目标在右儿子部
    updata(m);//回溯数据
    return ;
}

区间修改
暴力扫一遍超时———打标记!!
注:
标记:对节点本身已做过操作,对儿子未做过操作;
下放标记后,自身标记要清空,防止重复执行操作

修改后要注意在回溯时修改父亲节点的相应维护值,即updata

void add(int m,int v){//打标记
    t[m].sum+=(t[m].r-t[m].l+1)*v;//对自己执行操作
    t[m].ax+=v;
    return ;
}
void spread(int m){//标记下放
    if(t[m].ax){
        add(m<<1,t[m].ax);
        add(m<<1|1,t[m].ax);
        t[m].ax=0;//清空自身标记
    }
    return ;
}
void change(int m,int ll,int rr,int v){
    if(t[m].l>=ll&&t[m].r<=rr){
    //当前区间被完全包含在要修改的区间内
        add(m,v);
        return ;
    }
    spread(m);
    int mid=t[m].l+t[m].r>>1;
    if(mid>=ll) change(m<<1,ll,rr,v);
    if(mid<rr) change(m<<1|1,ll,rr,v);
    updata(m);
    return ;
}

3。查询
注:查询时要先下放区间修改的标记;

单点查询

long long  ask(int m,int wz){
    if(t[m].l==t[m].r) return v[wz]+t[m].ax;
    spread(m);
    int mid=t[m].l+t[m].r>>1;

    if(wz<=mid) return ask(m<<1,wz);
    else return ask(m<<1|1,wz);
}

区间查询

long long ask(int m,int ll,int rr){
    if(t[m].l>=ll&&t[m].r<=rr) return t[m].sum;
    spread(m);long long ans=0;
    int mid=t[m].l+t[m].r>>1;
    if(mid>=ll) ans+=ask(m<<1,ll,rr);
    if(mid<rr) ans+=ask(m<<1|1,ll,rr);
    return ans;
}
sys学长的笔记

http://loisys.cc/oi/note/segment-tree/

例题

线段树练习1
单点修改,区间查询;

http://codevs.cn/problem/1080/

#include<iostream>
#include<cstdio>
using namespace std;
const int N=100000;
int n,q,x,y,z,v[N];
struct seg_tree{
    int l,r;
    long long sum;
}t[N<<2];
void updata(int m){
    t[m].sum=t[m<<1].sum+t[m<<1|1].sum;
    return ;
}
void build(int m,int ll,int rr){
    t[m].l=ll;t[m].r=rr;
    if(ll==rr){
        t[m].sum=v[ll];
        return ;
    }
    int mid=ll+rr>>1;
    build(m<<1,ll,mid);
    build(m<<1|1,mid+1,rr);
    updata(m);
    return ;
}
void change(int m,int wz,int w){
    if(t[m].l==t[m].r){
        t[m].sum+=w;
        return ;
    }
    int mid=t[m].l+t[m].r>>1;
    if(wz<=mid) change(m<<1,wz,w);
    else change(m<<1|1,wz,w);
    updata(m);
    return ;
}
long long ask(int m,int ll,int rr){
    if(t[m].l>=ll&&t[m].r<=rr) return t[m].sum;
    int mid=t[m].l+t[m].r>>1;
    long long ans=0;
    if(mid>=ll) ans+=ask(m<<1,ll,rr);
    if(mid<rr) ans+=ask(m<<1|1,ll,rr);
    return ans; 
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&v[i]);
    build(1,1,n);
//  for(int i=1;i<=n*4;i++) printf("i=%d,l=%d,r=%d,sum=%d\n",i,t[i].l,t[i].r,t[i].sum);
    scanf("%d",&q);
    while(q--){
        scanf("%d%d%d",&x,&y,&z);
        if(x==1) change(1,y,z);
        else if(x==2) printf("%lld\n",ask(1,y,z));
    }
    return 0;
}

线段树练习2
区间修改,单点查询;

http://codevs.cn/problem/1081/

#include<iostream>
#include<cstdio>
using namespace std;
const int N=100000+50;
int  n,p,a,x,y,z,v[N];
struct seg_tree{
    int l,r,ax;
}t[N<<2];
void build(int m,int ll,int rr){
    t[m].l=ll,t[m].r=rr;
    if(ll==rr)  return ;
    int mid=ll+rr>>1;
    build(m<<1,ll,mid);
    build(m<<1|1,mid+1,rr);
    return ;
}
void add(int m,int vv){
    t[m].ax+=vv;
}
void spread(int m){
    if(t[m].ax) {
        add(m<<1,t[m].ax);
        add(m<<1|1,t[m].ax);
        t[m].ax=0;
    }
    return ;
}

void change(int m,int ll,int rr,int vv){
    if(t[m].l>=ll&&t[m].r<=rr)  {
        add(m,vv);return;
    }
    int mid=t[m].l+t[m].r>>1;
    spread(m);
    if(mid>=ll) change(m<<1,ll,rr,vv);
    if(mid<rr) change(m<<1|1,ll,rr,vv);
    return ;
}
int  ask(int m,int wz){
    if(t[m].l==t[m].r&&t[m].l==wz) return v[wz]+t[m].ax;
    int mid=t[m].l+t[m].r>>1;
    spread(m);
    if(wz<=mid) return ask(m<<1,wz);
    else return ask(m<<1|1,wz);
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&v[i]);
    build(1,1,n);
//  for(int i=1;i<=n*4;i++) printf("i=%d,l=%d,r=%d\n",i,t[i].l,t[i].r);
    scanf("%d",&p);
    while(p--){
        scanf("%d",&a);
        if(a==1) scanf("%d%d%d",&x,&y,&z),change(1,x,y,z);
        if(a==2) scanf("%d",&y),printf("%d\n",ask(1,y));
    }
}

线段树练习3
区间修改,区间查询;

http://blog.csdn.net/loi_lxt/article/details/64929096

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值