线段树总结

                  ——这个周末训练赛和codeforces,加上自己有点偷懒导致进度严重推迟

线段树,顾名思义是在树上的线段,通过建树来维护你需要的操作,基本的操作有:区间求和,区间求最值,区间异或(这个实际上和区间更新差不多,就是加上值这个操作换成了异或),区间覆盖,扫描线求面积,线段树求区间连续字段。

下面从最基础的区间求最值开始:

给你一个长度为n的序列,例如长度为5的序列{9,1,8,5,6},最大值和最小值,当然朴素算法在数据量小的时候是可以的,但是在n的数量级特别大的时候就显得略加笨重。由此想出一种办法,将数组里的数构成一棵数。如下图:

线段树总结_数组

 

这棵树中叶子保存的是每个下标的数值,两个叶子的父亲,保存的是叶子中最小的那一个,这样一直到根节点得出数列中最小值。最大值也是一样。说起来很简单,接着就是用代码实现,线段树是一个二叉树。数据量大的时候也可能是一个完全二叉树,用一个sum数组来存储每个节点的数值,然后递归建树。

void build(int i,int l,int r)
{
    if(l==r)
    {
        scanf("%d",&sum[i]);
        return ;
    }
    int m=(l+r)/2;
    build(i*2,l,m);
    build(i*2+1,m+1,r);
    pushup(i);//收集子节点的结果
}
         Pushup()函数是将当前节点向下更新
         void pushup(int i)
{
         sum[i]=min(sum[i*2],sum[i*2+1]);
}

当维护用途不同的时候push函数的用法是不一样的。下面每种用途的线段树push函数的写法都有讲解。

然后是更新操作:

void update(int id ,int val,int i,int l,int r)
{
         if(l==r)
         {
                  sum[i]=val;//这里的操作的修改id点的值
                  return;
         }
         int m=(l+r)/2;
         if(id<=m) update(id,val,i*2,l,m);
         else update(id,val,i*2+1,m+1,r);
         pushup(i);
}

修改,查询操作都是从根节点开始遍历,然后当你遍历到的当前区间,在需要区间之内的时候,就可以进行你需要的操作了。

查询操作:

查询的时候,一个区间的最值要不就在左区间,要不就在右区间,要不然就在左加右区间(虽然很像废话,但是就是这样的)

int query (int rt,int L,int R,int l,int r)
{
    if(L<=l&&r<=R)
        return sum[rt];
    int m=(r+l)>>1;
    int ret=0;
    if(L<=m)
        ret=min(ret,query(rt*2,L,R,l,m)
    if(R>m)
        ret=min(ret,query(rt*2+1,L,R,m+1,r);
    return ret;
}

区间求和(单点更新,区间更新):        

单点更新:

int sum[N*4];
void pushup(int i)
{
         sum[i]=sum[i*2]+sum[i*2+1];
}
void build(int i,int l,int r)
{
    if(l==r)
    {
        scanf("%d",&sum[i]);
        return ;
    }
 
    int m=(l+r)/2;
    build(i*2,l,m);
    build(i*2+1,m+1,r);
    pushup(i);//收集子节点的结果
}
/*
在当前区间[l, r]内查询区间[ql, qr]间的目标值  
且能执行这个函数的前提是:[l,r]与[ql,qr]的交集非空  
其实本函数返回的结果也是 它们交集的目标值  
*/
int query(int ql,int qr,int i,int l,int r)
{
         if(ql<=l&&r<=qr) return sum[i];
         
         int m=(l+r)/2;
         int cur=0;
         if(ql<=m) cur+=query(ql,qr,i*2,l,m);
         if(m<qr) cur+=query(ql,qr,i*2+1,m+1,r);
         return cur;
}
/*
update这个函数就有点定制的意味了  
本题是单点更新,所以是在区间[l,r]内使得第id数的值+val  
如果是区间更新,可以update的参数需要将id变为ql和qr  
*/
void update(int id ,int val,int i,int l,int r)
{
         if(l==r)
         {
                  sum[i]+=val;
                  return;
         }
         int m=(l+r)/2;
         if(id<=m) update(id,val,i*2,l,m);
         else update(id,val,i*2+1,m+1,r);
         pushup(i);
}

区间更新:

这里要引进一个概念叫:延迟更新。当你想要更新一个区间的时候人进一个数组addv,addv[i]表示以i为节点的树共同增加了(addv[i]),然后在通过递归,顺带更新带叶子结点。

const int MAXN=100000+100;
typedef long long LL;
#define lson i*2,l,m
#define rson i*2+1,m+1,r
LL sum[MAXN*4];
LL addv[MAXN*4];
void PushDown(int i,int num)//这就是延迟操作,更新当前结点的叶子
{
    if(addv[i])
    {
        sum[i*2] +=addv[i]*(num-(num/2));//每个点的需要更新的值乘以的个数
        sum[i*2+1] +=addv[i]*(num/2);//同上
        addv[i*2] +=addv[i];//这个区间需要更新的个数
        addv[i*2+1]+=addv[i];
        addv[i]=0;
    }
}
void PushUp(int i)
{
    sum[i]=sum[i*2]+sum[i*2+1];
}
void build(int i,int l,int r)
{
    addv[i]=0;//将延迟操作更改的值需要记录到addv数组中,现在将它初始化
    if(l==r)
    {
        scanf("%I64d",&sum[i]);
        return ;
    }
    int m=(l+r)/2;
    build(lson);
    build(rson);
    PushUp(i);
}
void update(int ql,int qr,int add,int i,int l,int r)
{
    if(ql<=l&&r<=qr)
    {
        addv[i]+=add;
        sum[i] += (LL)add*(r-l+1);
        return ;
    }
    PushDown(i,r-l+1);//向下更新枝叶的值
    int m=(l+r)/2;
    if(ql<=m) update(ql,qr,add,lson);
    if(m<qr) update(ql,qr,add,rson);
    PushUp(i);
}
LL query(int ql,int qr,int i,int l,int r)
{
    if(ql<=l&&r<=qr)
    {
        return sum[i];
    }
    PushDown(i,r-l+1);
    int m=(l+r)/2;
    LL res=0;
    if(ql<=m) res+=query(ql,qr,lson);
    if(m<qr) res+=query(ql,qr,rson);
    return res;
}

 

我每天都在努力,只是想证明我是认真的活着.