线段树的思路

Posted on 2018-10-28 09:47  亦辰落  阅读(254)  评论(0编辑  收藏  举报

  线段树是一种优化方法,一般来说,所有的树状数组都可以用线段树来做,但线段树的题目不一定能用树状数组来做。

  线段树可以这么理解:

  1. 其实就是把一组数据用一棵树分割成若干个节点保存。
  2. 然后,在树中查找所需要的数据即可。
  3. 即[L,R]区间范围内有n个点分成若干个子区间。
  4. 通过对这些少量子区间的修改或者统计,来实现快速对[L,R]的修改或者统计

  线段树的主要的代码有三种

  1. 建树(单点,区间)
  2. 查找(单点,区间)
  3. 修改
  4. 标记下传

  其实都和树状数组很像,具体的一棵线段树是这样纸的:

 

建立线段树的代码是这个样纸的:

void build(int Lchild,int Rchild,int k,int v){
//L,Rchild是指左右儿子;v是指要修改的值 
    int mid=(Lchild+Rchild)/2;//二分建树 
    if(Lchild==Rchild){
        mi[k]=v;//当然这里也可以这样理解
        //scanf("%d",mi[k]);这样应该会更好理解 
        return  ;
    }
    build(Lchild,mid,k*2);//建立左子树; 
    build(mid+1,Rchild,k*2+1);//建立右子树;
    mi[k]=min(mi[k*2],mi[k*2+1]);//mi维护区间最小值 
}

然后就是查找:

int query(int l,int r,int k,int x,int y){//查找
    if(x>r||y<l)//区间[x,y]没有被[l,r]包含 
        return 2147483647//则返回一个极大值
    if(x<=l&&y>=l)//询问区间在当前区间的值 
        return mi[k];//返回并维护区间最小值 
    if(x>=1&&y<=r)
        return min(query(k*2,mid),query(k*2+1,mid+1,r)); 
    //否则分别处理左右子区间 
}

接着是进行修改:

int  change(int l,int r,int k,int x,int v){//修改 
    if(x>r||x<l)
        return;
    if(l==r&&l==x){//l==x相当于r==x; 
        mi[k]=v;
        return;
    }
    change(l,mid,k*2,x,v);
    change(mid+1,k*2+1,x,v);//修改左右子树 
    mi[k]=min(mi[k*2],mi[k*2+1]);//继续维护区间最小值 
} 

and 标记下移:

void add(int k,int l,int r,int v){//给区间[l,r]所有的数加上v 
    mi[k]+=v;//打标记 
    sum[k]+=(r-l+1)*v;//维护对应的区间和 
    return;
}
void down(int k){//标记下传
    //判断
    if(mi[k]==0)//若无标记则不考虑此步操作 
        return ;//无需返回任何值 
    add(k*2,l,mid,mi[k]);// 下传到左子树 
    add(k*2+1,mid+1,r,mi[k]);//下传到右子树
    mi[k]=0;//标记清零 
}

last 区间修改:

void modify(int k,int l,int r,int x,int y,int v){//给定区间[x,y]所有数加上v; 
    if(x<=l&&y>=r)
        return add(k,l,r,v);
    int mid=(l+r>>1);
    down(k,l,r,mid);//没达到一个节点都需要下传一次标记 
    if(x<=mid)
        modify(k*2,l,mid,x,y,v); //修改左子树 
    if(mid<y) 
        modify(k*2+1,mid+1,r,x,y,v);// 修改右子树 
        sum[k]=sum[k*2]+sum[k*2+1];//下传后更新sum的值 
}

 唔,先到这里吧!