线段树模板+一些自己的理解

看了很多次终于理解了一些

因为要经常用到左右孩子节点,所以先定义两个函数用于计算左右孩子的序号:

inline lc(int x){return x<<1;}  //左儿子
inline rc(int x){return x<<1|1;}//右儿子

首先线段树是用二叉树的每一个节点表示了一个区间,最上面的节点表示了整个区间,然后下一层分别表示了上一层节点的一半,这样一层一层二分下去,直到最下面的叶子节点。
所以先看一下建树的代码:

void build(int p,int l,int r)   //建树
{
    if(l==r){
        t[p]=a[l];
        return ;
    }
    int mid=(l+r)>>1;
    build(lc(p),l,mid);
    build(rc(p),mid+1,r);
    up(p);
}

这是一个递归的过程,从上往下二分,直到叶子节点的时候(l==r)就等于原数组中对应的数。然后每一次往下二分的时候,因为树的节点的值变化了,所以他的父节点也会变化,所以每次向下递归之后都要向上更新一次,就是up()函数

void up(int p)  //向上更新树
{
    //t[p]=max(t[lc(p)],t[rc(p)]); //维护最值
    t[p]=t[lc(p)]+t[rc(p)];     //维护和
}

然后说一下懒标记
更新线段树,节点需要从上更到下,修改n个值的话就会更新很多个节点。但有时候并不是所有更新的节点都会用到,所以用懒标记来节省操作。懒标记就是当树上的一个节点需要变化时,在这个节点打上懒标记,懒标记的值就是变化量,之后更新这个节点,然后就不往下更新了。在之后进行每一次更新或查询操作时,再向下更新一层。就是指在需要用到这个节点的时候才更新,否则不更新。这里懒标记用lazy[]数组,向下更新用down()函数

void down(int p,int l,int r)    //懒标记下传
{
    if(lazy[p]){	//首先判断是否需要向下更新
        //懒标记下传
        lazy[lc(p)]+=lazy[p];
        lazy[rc(p)]+=lazy[p];
        //更新下一层的两个儿子节点
        int mid=(l+r)>>1;
        t[lc(p)]+=lazy[p]*(mid-l+1);
        t[rc(p)]+=lazy[p]*(r-mid+1);
        //当前这一层懒标记清0
        lazy[p]=0;
    }
}

之后结合懒标记进行更新操作

void update(int xl,int xr,int x,int p,int l,int r)
{//xl,xr为要修改的区间
 //x为变化的值
 //p为当前节点编号
 //l,r为当前区间
    if(xl<=l && xr>=r){
        //t[p]=x;
        lazy[p]+=x; //管辖当前区间的节点加上懒标记
        t[p]+=x*(r-l+1);    //修改树上的节点
        return ;
    }
    down(p,l,r);    //下传一次懒标记
    int mid=(l+r)>>1;
    if(xl<=mid) update(xl,xr,x,lc(p),l,mid);
    if(xr>mid) update(xl,xr,x,rc(p),mid+1,r);
    up(p);  //向上更新维护
}

查询操作:

int query(int xl,int xr,int l,int r,int p)
{//xl,xr为要修改的区间
 //l,r为当前区间
 //p为当前节点编号
    if(xl<=l && r<=xr) return t[p];
    down(p,l,r);    //懒标记下传一层
    int mid=(l+r)>>1;
    int ans=0;
    if(xl<=mid){
        //ans=max(query(xl,xr,l,mid,lc(p)),ans);    //查询最值的操作
        ans+=query(xl,xr,l,mid,lc(p));
    }
    if(xr>mid){
        //ans=max(query(xl,xr,mid+1,r,rc(p)),ans);
        ans+=query(xl,xr,mid+1,r,rc(p));
    }
    return ans;
}

之后拼一拼就可以用了
以hdoj的1166为例:

#include <bits/stdc++.h>
using namespace std;

#define rg register
#define putln putchar('\n')
#define debug(x) cout<<"@ "<<(x)<<endl
#define rep(i,a,b) for(rg int i=a;i<=b;++i)
#define per(i,a,b) for(rg int i=a;i>=b;--i)

typedef long long ll;
const int MXN = 2e5+5;
int a[MXN];
int t[4*MXN],lazy[4*MXN]; //t是树,lazy是懒标记
int n,m,l,r,p;
string s;
inline lc(int x){return x<<1;}  //左儿子
inline rc(int x){return x<<1|1;}//右儿子

void up(int p)  //向上更新树
{
    //t[p]=max(t[lc(p)],t[rc(p)]); //维护最值
    t[p]=t[lc(p)]+t[rc(p)];     //维护和
}
void down(int p,int l,int r)    //懒标记下传
{
    if(lazy[p]){
        //懒标记下传
        lazy[lc(p)]+=lazy[p];
        lazy[rc(p)]+=lazy[p];
        //更新下一层的两个儿子节点
        int mid=(l+r)>>1;
        t[lc(p)]+=lazy[p]*(mid-l+1);
        t[rc(p)]+=lazy[p]*(r-mid+1);
        //当前这一层懒标记清0
        lazy[p]=0;
    }
}
void build(int p,int l,int r)   //建树
{
    if(l==r){
        t[p]=a[l];
        return ;
    }
    int mid=(l+r)>>1;
    build(lc(p),l,mid);
    build(rc(p),mid+1,r);
    up(p);
}
void update(int xl,int xr,int x,int p,int l,int r)
{//xl,xr为要修改的区间
 //x为变化的值
 //p为当前节点编号
 //l,r为当前区间
    if(xl<=l && xr>=r){
        //t[p]=x;
        lazy[p]+=x; //管辖当前区间的节点加上懒标记
        t[p]+=x*(r-l+1);    //修改树上的节点
        return ;
    }
    down(p,l,r);    //下传一次懒标记
    int mid=(l+r)>>1;
    if(xl<=mid) update(xl,xr,x,lc(p),l,mid);
    if(xr>mid) update(xl,xr,x,rc(p),mid+1,r);
    up(p);  //向上更新维护
}
int query(int xl,int xr,int l,int r,int p)
{//xl,xr为要修改的区间
 //l,r为当前区间
 //p为当前节点编号
    if(xl<=l && r<=xr) return t[p];
    down(p,l,r);    //懒标记下传一层
    int mid=(l+r)>>1;
    int ans=0;
    if(xl<=mid){
        //ans=max(query(xl,xr,l,mid,lc(p)),ans);    //查询最值的操作
        ans+=query(xl,xr,l,mid,lc(p));
    }
    if(xr>mid){
        //ans=max(query(xl,xr,mid+1,r,rc(p)),ans);
        ans+=query(xl,xr,mid+1,r,rc(p));
    }
    return ans;
}
int main()
{
    scanf("%d",&p);
    rep(i,1,p){
        memset(t,0,sizeof(t));
        memset(lazy,0,sizeof(lazy));
        scanf("%d",&n);
        rep(j,1,n) scanf("%d",&a[j]);
        build(1,1,n);
        printf("Case %d:\n",i);
        while(cin>>s&&s!="End"){
            scanf("%d %d",&l,&r);
            if(s=="Query") printf("%d\n",query(l,r,1,n,1));
            if(s=="Add") update(l,l,r,1,1,n);
            if(s=="Sub") update(l,l,-r,1,1,n);
        }
    }
    return 0;
}
上一篇:【转载】Postgres xc 和 xl


下一篇:material-ui入门之定制化Breakpoints