线段树(区间操作+lazy) 很好的模板以及心得体会

参考博客https://blog.csdn.net/neighthorn/article/details/52115830

这里只讨论区间处理+lazy

acm比赛考察不会出裸的模板,所以必须深刻理解线段树原理,各函数的目的。

拿到问题,简单数学化题目要求 , 比如说这题

poj 3667

题意: 

两种操作 1)订房间 给定长度x ,求连续长度>=x 的区间起始位置, 尽量靠左 , 查询到结果后把该段长度标记为不可用

2) 退房间 给定xy,把xy区间标记为可用

思路:

操作1 等价于两部, 1 查询到可用区间的起始位置, 2 更新该段区间为不可用

操作2 等价于更新该段区间为可用

分析 :

数据结构

我们要知道线段树数据结构需要哪些内容, l,r,lazy (必备) 、v(区间[l,r]内连续最大区间,用于查询时判断是否进入这个区间)、llen,rlen(区间[l,r]的前缀, 后缀长度。 举例分析 ,假设n为6,所需x为4 . 则一开始进入[1,6]。分成两个区间[1,3][4,6[ 。这个时候左子树的后缀 + 右子树的前缀可以符合条件,我们就要把答左子树的后缀起点就是我们要的案)

扫描二维码关注公众号,回复: 2447572 查看本文章
struct node
{
    int l,r,v,lazy,llen,rlen;
    void changeLen()
    {
        llen = rlen = v = lazy*(r-l+1);
    }
}node[N<<2];    //  线段树的空间大概是数组空间的4倍;

changelen()的作用,lazy标记了区间[l,r]的可用性(整体),那么[l,r]内的v,llen,rlen 就等于 区间长度*lazy。

通过lazy的改变,修改最长连续区间、左前缀、右后缀的值

build 建树

定位到叶子节点后,标记lazy的1(通过changelen函数,修改v,llen, rlen 为 1) 。

存储信息:node[numb].l = l , node[numb].r = r(必须的,线段树的基本)

void build(int l,int r,int numb)    //  线段树的建立;
{
    node[numb].lazy=1;
    node[numb].l = l;
    node[numb].r = r;
    node[numb].changeLen();
    if(l==r) return;
    int mid=(l+r)>>1;
    build(l,mid,numb<<1);
    build(mid+1,r,numb<<1|1);
}

change 更新操作

更新操作模板的架构基本是这样的 


if (区间重合,即定位到目标区间)
{
    lazy标记 // 节约时间,不向下走
    return;
}

// 没有定位到目标区间,那么向下一层走

if(node[numb].lazy != -1) // 向下走之前,倘若节点上有lazy标志,那么先更新子树信息,才能向下走
    pushDown(numb)
mid = (node[numb].l + node[numb].r) / 2;
if(l>mid) // 目标区间 在本节点的右子树上
    更新右边
else if(r<=mid)
    更新左边
else{ // 左右各占一部分,分别更新
    更新左
    更新右
}
pushUp(numb); // 更新完左右子树,那么本节点的信息也要修改

本题中,我们要明白更新时,那些参数是需要改变的,

1)我们定位到目标区间,更新其lazy,马上能得到该区间上的 v=llen = rlen因此changelen()

2)PushDown ,向下传递,那么子节点上的 lazy 就该被更新为 父节点上的lazy (这里注意,本题的lazy取值只有1可用,0不可用,-1没有lazy , 其他题有的lazy暂存的是操作次数那么应该累加,比如对区间做加法,那么向下传递应该是node[rt<<1].lazy+=node[rt].lazy)。并且同时更新子节点上的v,llen,rlen

3)PushUp , 用左右节点的信息更新父节点。逐个分析父节点的各个变量 ,

 l r lazy :跟子树无关 。

v:   修改为 左节点的v 和 右节点的v 以及 左节点后缀 + 右节点前缀 三者的最大值

llen:  左节点的前缀, 当左节点区间都是可用时,那么还需加上右节点的前缀 (两个区间能连起来)

rlen: 右节点的后缀, 当右节点区间都是可用时,还需加上左节点的后缀

void PushDown(int numb)             //  向下往左右儿子方向更新数据;
{
    // 为了父节点的lazy 对左右子树的影响,要思考lazy会影响左右子树的什么
    // 本题,lazy肯定要向下传递,子树再根据lazy ,修改自身变量
    //(标记为0 相当于该区间完全被占用,也就是llen rlen v = 0 )

    // 最后 把父节点的 lazy 标记取消 变成-1
    node[numb<<1].lazy=node[numb].lazy;
    node[numb<<1|1].lazy=node[numb].lazy;
    node[numb<<1].changeLen();
    node[numb<<1|1].changeLen();

    node[numb].lazy=-1;              //  更新完了要清零;
}
void PushUp(int numb)               
{
    // 假设父节点是A ,左右子树分别是BC
    // pushUp时 关心BC,对A的影响

    // 1) A的连续区间最大长 v =max( B.v,C.v ,B.rlen+C.llen)
    // 2) A的llen  = B.llen 。 且当 B整只为1 A.len += C.llen
    // 3) A的rlen = C.rlen  且当 C整只为1  A.rlen += B.rlen

    int tmp = max(node[numb<<1].v, node[numb<<1|1].v);
    node[numb].v = max(tmp, node[numb<<1].rlen + node[numb<<1|1].llen);
    // 更新根节点的 区间内最长区间值
    node[numb].llen = node[numb<<1].llen;
    node[numb].rlen = node[numb<<1|1].rlen;

    if(node[numb<<1].v == node[numb<<1].r-node[numb<<1].l + 1)
        node[numb].llen +=  node[numb<<1|1].llen;

    if(node[numb<<1|1].v == node[numb<<1|1].r - node[numb<<1|1].l + 1)
        node[numb].rlen += node[numb<<1].rlen ;

}
void change(int l,int r,int numb,int val)   //  插入更新数据;
{
    if(node[numb].l==l&&node[numb].r==r)    //  如果区间完全重合,则不需要再往下更新了,先保存起来,可以节约很多的时间(lazy思想)
    {
        node[numb].lazy=val;
        node[numb].changeLen();
        return;
    }
    if(node[numb].lazy!=-1) PushDown(numb);     //  因为没有找到完全重合的区间,所以要先更新下一层区间;
    int mid=(node[numb].r+node[numb].l)>>1;

    if(l>mid) change(l,r,numb<<1|1,val);
    else if(r<=mid) change(l,r,numb<<1,val);
    else{
        change(l,mid,numb<<1,val);
        change(mid+1,r,numb<<1|1,val);
    }
    PushUp(numb);       //  最后还得往上返回,更新父节点区间;
}

query查询

if 查询到目标区间
    返回节点上的信息 // 有时候是最大值,有时候是和
    retrurn;

if lazy 
    pushDown() // 向下找之前,有lazy标记必须先向下更新


mid = (node[numb].l + node[numb].r) / 2;

if(l>mid) // 进入右子树
    return 查询右子树
else if(r<=mid) //进入左子树
    return 查询左子树
else 
{
    // 左右子树都占一部分
    // 返回值 需要就题论题,
    int tmp1 = query(l,mid,numb<<1);
    int tmp2 = query(mid+1,r,numb<<1|1);

    // 求最大值 return max(tmp1,tmp2);
    // 求和 return tmp1+tmp2;
    
    // 不过本题有不一样的需求
}

分析本题, 需求是找到 满足区间的起始位置, 而且尽可能靠左。 那么判断条件就应是 v  ,llen ,rlen 

1、如果左儿子的v>=x那么就返回query(左儿子)
2、如果左儿子的后缀+右儿子的前缀>=x,直接返回左儿子的后缀开始的下标
3、若果右儿子的v>=x那么就返回query(右儿子)
4、那就只能返回0了(忧桑~~>_<)

由此可见不同题目要求,判断条件,返回条件也要顺应着修改

int query(int l,int r,int numb, int len)
{
    if(node[numb].l==node[numb].r&&len == 1){
        return node[numb].l;
    }
    if(node[numb].lazy!=-1) PushDown(numb);    
    int mid=(node[numb].r+node[numb].l)>>1;
    // 若左边区间大于等于val 则去左边查
    // 若左边的后缀 + 右边的前缀 >= val ,则返回左边后缀的起点
    // 若右边区间大于等于val ,则去右边
    if(node[numb<<1].v >= len)
        return query(l,mid,numb<<1,len);
    if(node[numb<<1].rlen + node[numb<<1|1].llen >= len)
        return node[numb<<1].r - node[numb<<1].rlen + 1;
    if(node[numb<<1|1].v >= len)
        return query(mid+1,r,numb<<1|1, len);
    return 0;
}

完整代码

#include<iostream>
#include<string>
#include<queue>
#include<map>
#include<set>
#include<vector>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=1000005;
int ans[N];
struct node
{
    int l,r,v,lazy,llen,rlen;
    void changeLen()
    {
        llen = rlen = v = lazy*(r-l+1);
    }
}node[N<<2];    //  线段树的空间大概是数组空间的4倍;

void build(int l,int r,int numb)    //  线段树的建立;
{
    node[numb].lazy=1;
    node[numb].l = l;
    node[numb].r = r;
    node[numb].changeLen();
    if(l==r) return;
    int mid=(l+r)>>1;
    build(l,mid,numb<<1);
    build(mid+1,r,numb<<1|1);
}
void PushUp(int numb)               
{
    // 假设父节点是A ,左右子树分别是BC
    // pushUp时 关心BC,对A的影响

    // 1) A的连续区间最大长 v =max( B.v,C.v ,B.rlen+C.llen)
    // 2) A的llen  = B.llen 。 且当 B整只为1 A.len += C.llen
    // 3) A的rlen = C.rlen  且当 C整只为1  A.rlen += B.rlen

    int tmp = max(node[numb<<1].v, node[numb<<1|1].v);
    node[numb].v = max(tmp, node[numb<<1].rlen + node[numb<<1|1].llen);
    // 更新根节点的 区间内最长区间值
    node[numb].llen = node[numb<<1].llen;
    node[numb].rlen = node[numb<<1|1].rlen;

    if(node[numb<<1].v == node[numb<<1].r-node[numb<<1].l + 1)
        node[numb].llen +=  node[numb<<1|1].llen;

    if(node[numb<<1|1].v == node[numb<<1|1].r - node[numb<<1|1].l + 1)
        node[numb].rlen += node[numb<<1].rlen ;

}
void PushDown(int numb)             //  向下往左右儿子方向更新数据;
{
    // 为了父节点的lazy 对左右子树的影响,要思考lazy会影响左右子树的什么
    // 本题,lazy肯定要向下传递,子树再根据lazy ,修改自身变量
    //(标记为0 相当于该区间完全被占用,也就是llen rlen v = 0 )

    // 最后 把父节点的 lazy 标记取消 变成-1
    node[numb<<1].lazy=node[numb].lazy;
    node[numb<<1|1].lazy=node[numb].lazy;
    node[numb<<1].changeLen();
    node[numb<<1|1].changeLen();

    node[numb].lazy=-1;              //  更新完了要清零;
}



void change(int l,int r,int numb,int val)   //  插入更新数据;
{
    if(node[numb].l==l&&node[numb].r==r)    //  如果区间完全重合,则不需要再往下更新了,先保存起来,可以节约很多的时间(lazy思想)
    {
        node[numb].lazy=val;
        node[numb].changeLen();
        return;
    }
    if(node[numb].lazy!=-1) PushDown(numb);     //  因为没有找到完全重合的区间,所以要先更新下一层区间;
    int mid=(node[numb].r+node[numb].l)>>1;

    if(l>mid) change(l,r,numb<<1|1,val);
    else if(r<=mid) change(l,r,numb<<1,val);
    else{
        change(l,mid,numb<<1,val);
        change(mid+1,r,numb<<1|1,val);
    }
    PushUp(numb);       //  最后还得往上返回,更新父节点区间;
}

// 查询到 连续区间>=len的 返回左边第一个坐标
int query(int l,int r,int numb, int len)
{
    if(node[numb].l==node[numb].r&&len == 1){
        return node[numb].l;
    }
    if(node[numb].lazy!=-1) PushDown(numb);    
    int mid=(node[numb].r+node[numb].l)>>1;
    // 若左边区间大于等于val 则去左边查
    // 若左边的后缀 + 右边的前缀 >= val ,则返回左边后缀的起点
    // 若右边区间大于等于val ,则去右边
    if(node[numb<<1].v >= len)
        return query(l,mid,numb<<1,len);
    if(node[numb<<1].rlen + node[numb<<1|1].llen >= len)
        return node[numb<<1].r - node[numb<<1].rlen + 1;
    if(node[numb<<1|1].v >= len)
        return query(mid+1,r,numb<<1|1, len);
    return 0;
}

int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    build(1,n,1);
    while(m--)
    {
        int x,y,z;
        scanf("%d",&z);
        if(z==1){
            scanf("%d",&x),cout<<(y=query(1,n,1,x))<<endl;
            if(y!=0)
                change(y,y+x-1,1,0);

        }
        else
            scanf("%d%d",&x,&y),change(x,x+y-1,1,1);
    }

    return 0;
}

HDU 6315 Naive Operations

1、分析数据结构

1、lazy:  加1操作时,如果该节点以下的子节点+1 不会产生贡献, 那么本次操作寄存在该节点上,即lazy+1
2、num:  该节点管辖的区间下,还差多少能产生贡献度,如果能产生贡献度,那么必须更新到叶子节点上
3、v : 贡献度
4、b:  分母b数组

2、更新操作

当查找到目标区间,对目标区间内的num--,

如果减完的num >0 ,那么本次操作寄存到lazy 。

当num ==0 ,则表明产生了贡献,那么此时需要更新到叶子节点

pushUp 保证了父节点的num、v 受到左右子节点影响,到底层更新节点,回溯回去后就更新。

void Insert(int l,int r,int numb)   //  插入更新数据;
{
    if(node[numb].l==l&&node[numb].r==r) // 找到目标区间 ,那么区间上的 num--, 因为这段区间是被更新的
    {
        node[numb].num --;
        if(node[numb].num > 0)
        {
            // 还产生价值
            node[numb].lazy ++ ;// 找到目标区间,而且未产生价值,那么这段区间以下的 加操作先存到lazy里。
            return ;
        }
        else
        {
            if(node[numb].l == node[numb].r) // 当num == 0 , 则必须要更新到叶子节点
            {
                node[numb].v ++;
                node[numb].num = node[numb].b;
                node[numb].lazy = 0;
                return;
            }
        }

    }
    if(node[numb].lazy)
        PushDown(numb);
    int mid=(node[numb].r+node[numb].l)>>1;
    if(l>mid)
        Insert(l,r,numb<<1|1);
    else if(r<=mid)
        Insert(l,r,numb<<1);
    else
    {
        Insert(l,mid,numb<<1);
        Insert(mid+1,r,numb<<1|1);
    }
    PushUp(numb);
}

完整代码


#include<iostream>
#include<string>
#include<queue>
#include<map>
#include<set>
#include<vector>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=1000005;
int ans[N];
int b[N];

struct node
{
    int l,r,v,lazy,b,num; // v: 值 ,b: 输入的b数组  num: 每个节点上 差多少能再得到一个满足的 最小值
        // lazy 标记为该节点以下 ,还得加多少次, 先寄存

} node[N<<2];   //  线段树的空间大概是数组空间的4倍;

void build(int l,int r,int numb)    //  线段树的建立;
{
    node[numb].l=l;
    node[numb].r=r;
    node[numb].v=0;
    node[numb].lazy=0;              //  用了lazy思想,提高了效率;
    if(l==r)
    {
        node[numb].b = b[l];
        node[numb].num = b[l];
        //cout<<b[l]<<' ';
        return;
    }
    int mid=(l+r)>>1;
    build(l,mid,numb<<1);
    build(mid+1,r,numb<<1|1);
    node[numb].num = min(node[numb<<1].num, node[numb<<1|1].num);
}
void PushUp(int numb)
{
    node[numb].v= node[numb<<1].v + node[numb<<1|1].v;
    node[numb].num = min(node[numb<<1].num,node[numb<<1|1].num );
}
void PushDown(int numb)             //  向下往左右儿子方向更新数据;
{
    node[numb<<1].lazy+=node[numb].lazy; //  寄存肯定是要 += 
    node[numb<<1|1].lazy+=node[numb].lazy;
    node[numb<<1].num-=node[numb].lazy;
    node[numb<<1|1].num-=node[numb].lazy;
    node[numb].lazy=0;              //  更新完了要清零;
}
void Insert(int l,int r,int numb)   //  插入更新数据;
{
    if(node[numb].l==l&&node[numb].r==r) // 找到目标区间 ,那么区间上的 num--, 因为这段区间是被更新的
    {
        node[numb].num --;
        if(node[numb].num > 0)
        {
            // 还产生价值
            node[numb].lazy ++ ;// 找到目标区间,而且未产生价值,那么这段区间以下的 加操作先存到lazy里。
            return ;
        }
        else
        {
            if(node[numb].l == node[numb].r) // 当num == 0 , 则必须要更新到叶子节点
            {
                node[numb].v ++;
                node[numb].num = node[numb].b;
                node[numb].lazy = 0;
                return;
            }
        }

    }
    if(node[numb].lazy)
        PushDown(numb);
    int mid=(node[numb].r+node[numb].l)>>1;
    if(l>mid)
        Insert(l,r,numb<<1|1);
    else if(r<=mid)
        Insert(l,r,numb<<1);
    else
    {
        Insert(l,mid,numb<<1);
        Insert(mid+1,r,numb<<1|1);
    }
    PushUp(numb);
}
int query(int l,int r,int numb)
{
    if(node[numb].l==l&&node[numb].r==r)
    {
        return node[numb].v;
    }
    if(node[numb].lazy)
        PushDown(numb);
    int mid=(node[numb].r+node[numb].l)>>1;
    if(l>mid)
        return query(l,r,numb<<1|1);
    else if(r<=mid)
        return query(l,r,numb<<1);
    else
    {
        return query(l,mid,numb<<1) + query(mid+1,r,numb<<1|1) ;
    }
}
int main()
{
    char str[10];
    int x, y,n,m;
    while(~scanf("%d%d", &n, &m))
    {
        for(int i=1; i<=n; i++)
            scanf("%d",&b[i]);
        build(1, n, 1);
        for(int i = 0; i < m; i ++)
        {
            scanf("%s%d%d", str, &x, &y);
            if(str[0] == 'a')
            {
                Insert(x, y, 1);
            }
            else
                printf("%d\n", query(x, y, 1));
        }
    }
    return 0;
}
/*
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<string>
#include<math.h>
#include<cstdlib>
#include<stdlib.h>
#include<queue>
#include<map>
#include<set>
#include<stack>
#define bug printf("*********\n");
#define mem0(a) memset(a, 0, sizeof(a));
#define mem1(a) memset(a, -1, sizeof(a));
#define finf(a, n) fill(a, a+n, INF);
#define in1(a) scanf("%d" ,&a);
#define in2(a, b) scanf("%d%d", &a, &b);
#define in3(a, b, c) scanf("%d%d%d", &a, &b, &c);
#define out1(a) printf("%d\n", a);
#define out2(a, b) printf("%d %d\n", a, b);
#define pb(G, b) G.push_back(b);
#pragma comment(linker, "/STACK:102400000,102400000")
using namespace std;
typedef long long LL;
typedef pair<LL, pair<int, LL> > LLppar;
typedef pair<int, int> par;
typedef pair<LL, int> LLpar;
const int mod = 998244353;
const LL INF = 1e9+7;
const int N = 1010;
const double pi = 3.1415926;

int n, m, mi;

struct node
{
    int l;
    int r;
    int num;  //节点当前状态,为0时意味着达到整除状态
    int lazy; //懒惰标记
    int sum; //整除结果
    int b; //b数组初始值
}e[100010*4];

void build(int l, int r, int k)
{
    e[k].l = l;
    e[k].r = r;
    e[k].sum = 0;
    e[k].lazy = 0;
    if(l == r) {
        scanf("%d", &e[k].b);
        e[k].sum = 0;
        e[k].num = e[k].b;
        return;
    }
    int mid = (l+r)/2;
    build(l, mid, 2*k);
    build(mid+1, r, 2*k+1);
    e[k].num = min(e[2*k].num, e[2*k+1].num); //维护区间最小值
}

void push(int k)
{
    if(e[k].lazy) {
        e[2*k].num += e[k].lazy;
        e[2*k+1].num += e[k].lazy;
        e[2*k].lazy += e[k].lazy;
        e[2*k+1].lazy += e[k].lazy;
        e[k].lazy = 0;
    }
}

void update(int l, int r, int k)
{
    if(e[k].l == l && e[k].r == r) {
        e[k].num --;
        if(e[k].num) {
        e[k].lazy --; //该区间没有可以整除的节点
            return;
        }else {
            if(e[k].l == e[k].r) { //找到达到整除状态的节点
                e[k].sum ++;
                e[k].num = e[k].b; //更新
                return;
            }
        }
    }
    push(k);
    int mid = (e[k].l+e[k].r)/2;
    if(r <= mid) update(l, r, 2*k);
    else if(l > mid) update(l, r, 2*k+1);
    else {
        update(l, mid, 2*k);
        update(mid+1, r, 2*k+1);
    }
    e[k].num = min(e[2*k].num, e[2*k+1].num); //维护区间最小值
    e[k].sum = e[2*k].sum + e[2*k+1].sum;
}

int quary(int l, int r, int k)
{
    if(e[k].l == l && e[k].r == r) {
        return e[k].sum;
    }
    int mid = (e[k].l+e[k].r)/2;
    if(r <= mid) return quary(l, r, 2*k);
    else if(l > mid) return quary(l ,r, 2*k+1);
    else {
        return quary(l, mid, 2*k) + quary(mid+1, r, 2*k+1);
    }
}

int main()
{
    char str[10];
    int x, y;
    while(~scanf("%d%d", &n, &m)) {
        build(1, n, 1);
        for(int i = 0; i < m; i ++) {
            scanf("%s%d%d", str, &x, &y);
            if(str[0] == 'a') {
                update(x, y, 1);
            }else printf("%d\n", quary(x, y, 1));
        }
    }
    return 0;
}
*/

猜你喜欢

转载自blog.csdn.net/qq_37591656/article/details/81232798