学习笔记 --- 线段树

开始切割数据结构,种树之路,学习了一下线段树,写一下心得与体会

一线段树摆上:
线段树...
线段树的原理:将[1,n]分解成若干特定的子区间(数量不超过4*n),然后,将每个区间[L,R]都分解为少量特定的子区间,通过对这些少量子区间的修改或者统计,来实现快速对[L,R]的修改或者统计。
线段树中有如下定理:
定理:n>=3时,一个[1,n]的线段树可以将[1,n]的任意子区间[L,R]分解为不超过个子区间。
定理:n>=3时,一个[1,n]的线段树可以将[1,n]的任意子区间[L,R]分解为不超过个子区间。
一般存储采用数组直接存储:
当前点的位置为now,那么它的左子树位置为now*2,右子树位置为now*2+1(和二叉树存储同理)
那么是线段树的模板(此处以一个维护和的线段树演绎)

建树过程:

void updata(int now)
{
    sum[now]=sum[now<<1]+sum[now<<1|1];
}//求和过程 
//此过程线段树核心过程,一般不同的线段树,最大的不同处就在此处
void build(int left,int right,int now)
{
    if (left==right)
        {
            sum[now]=tree[left];
            return;
        }//每当达到叶节点就把值更新一下
    int mid=(left+right)>>1;
    build(left,mid,now<<1);//左子树建树
    build(mid+1,right,now<<1|1);//右子树建树
    updata(now); //容易遗忘但是特别重要的一个语句,在更新完子数后根据子数修改根
}//建树过程 

点修改过程:
更新一个点的值

void point_change(int now,int left,int right,int data,int locate)
{
    if (left==right)
        {
            sum[now]+=data;
            return;
        }//如果到达这个点的位置,更新
    int mid=(left+right)>>1;
    if (locate<=mid)//如果要修改的点位于左子树,向左查询
        point_change(now<<1,left,mid,data,locate);
    else//反之向右
        point_change(now<<1|1,mid+1,right,data,locate);
    updata(now);//仍旧不能忘,更新完点就要更新根
} //点修改过程 

区间修改过程:
更新区间【left,right】的值

标记的含义:
本节点的统计信息已经根据标记更新过了,但是本节点的子节点仍需要进行更新。即,如果要给一个区间的所有值都加上1,那么,实际上并没有给这个区间的所有值都加上1,而是打个标记,记下来,这个节点所包含的区间需要加1.打上标记后,要根据标记更新本节点的统计信息,比如,如果本节点维护的是区间和,而本节点包含5个数,那么,打上+1的标记之后,要给本节点维护的和+5。这是向下延迟修改,但是向上显示的信息是修改以后的信息,所以查询的时候可以得到正确的结果。有的标记之间会相互影响,所以比较简单的做法是,每递归到一个区间,首先下推标记(若本节点有标记,就下推标记),然后再打上新的标记,这样仍然每个区间操作的复杂度是O(log2(n))。

标记有相对标记和绝对标记之分:
相对标记是将区间的所有数+a之类的操作,标记之间可以共存,跟打标记的顺序无关(跟顺序无关才是重点)。
所以,可以在区间修改的时候不下推标记,留到查询的时候再下推。绝对标记是将区间的所有数变成a之类的操作,打标记的顺序直接影响结果,所以这种标记在区间修改的时候必须下推旧标记,不然会出错。

注意,有多个标记的时候,标记下推的顺序也很重要,错误的下推顺序可能会导致错误。

void section_change(int LEFT,int RIGHT,int data,int left,int right,int now)//LEFT,RIGHT表示需要更新的区间,data为更新值,left,right为当前点的控制区间,now当前位置
{
    if (LEFT<=left && RIGHT>=right)//如果本区间完全在更新的区间范围,就更新
        {
            sum[now]+=data*(right-left+1);
            delta[now]+=data;//惰性标记,子区间的值需要依此修改
            return;
        }
    int mid=(left+right)>>1;
    if (LEFT<=mid)//判断左子树和修改区间有无交集,有交集则递归
        section_change(LEFT,RIGHT,data,left,mid,now<<1);
    if (RIGHT>mid)//原理同上
        section_change(LEFT,RIGHT,data,mid+1,right,now<<1|1);
    updata(now);//同理需要更新根
}//区间修改过程

区间查询过程:
查询区间【left,right】的值

void pushdown(int now,int leftn,int rightn)//把根的标记下放到子数,确保答案的正确性
{
    if (delta[now]!=0)
        {
            delta[now<<1]+=delta[now];
            delta[now<<1|1]+=delta[now];//标记下推
            sum[now<<1]+=delta[now]*leftn;
            sum[now<<1|1]+=delta[now]*rightn;//修改子节点的值
            delta[now]=0;//清除当前点的标记
        } 
}//标记下放函数

int query(int LEFT,int RIGHT,int left,int right,int now)
{
    if (LEFT<=left && RIGHT>=right)
        return sum[now];//在区间内,直接返回即可
    int mid=(left+right)>>1;
    pushdown(now,mid-left+1,right-mid);//标记下推(不加这步,结果出错)
    int total=0;//累计答案
    if (LEFT<=mid) total+=query(LEFT,RIGHT,left,mid,now<<1);
    if (RIGHT>mid) total+=query(LEFT,RIGHT,mid+1,right,now<<1|1);
    return total;       
} //区间查询    

主过程的调用:

int main()
{
//建树   
Build(1,n,1);   
//点修改  
Update(L,C,1,n,1);  
//区间修改   
Update(L,R,C,1,n,1);  
//区间查询   
int ANS=Query(L,R,1,n,1); 
}

说了这么多,还是需要大量题目的练习( ⊙ o ⊙ )啊!…..

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值