初识线段树

本文介绍了线段树这一数据结构,并通过一个序列操作问题详细阐述了线段树如何支持区间加法、修改和查询操作。在解决过程中,强调了lazy propagation策略的细节,包括判断顺序和值的清零处理。此外,还提醒读者在初始化时要注意处理值为0的情况。
摘要由CSDN通过智能技术生成

序列操作

【问题描述】

  给出一个有n个元素的的数组:A[1],A[2],…,A[n],你的任务是设计一个数据结构,支持以下三种操作:
  1 L R v:把A[L],A[L+1],…A[R]都增加v(v>=0)。
  2 L R v:把A[L],A[L+1],…A[R]都修改成(v>=0)。
  3 L R:计算A[L],A[L+1],…A[R]的元素和、最小值和最大值。
  输入保证任意时刻序列中所有元素和不超过10^9。
  
【输入格式】

  第一行包含 2 个正整数:n 和 m 。以下n行,每行一个整数,表示 A[i]。再以下 m 行,每行为上述三种操作之一。

【输出格式】

  针对操作类型3,依次输出元素和、最小值和最大值。

【输入样例】

10 7
1 2 3 4 5 6 7 8 9 10
3 4 8
1 3 7 2
2 4 8 5
3 3 7
1 6 9 1
2 3 5 0
3 4 7

【输出样例】
30 4 8
25 5 5
12 0 6

【数据范围】

1<=m,n<=100 000。
最初始的|A[i]|<=100。


#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<stack>
#include<queue>
#include<set>
#include<map>
#include<vector>
#include<cctype>
using namespace std;
const int maxn=100005;
int minv[2*maxn],np,rt,maxv[2*maxn],sum[2*maxn],add[2*maxn],setv[2*maxn],a[maxn],lc[2*maxn],rc[2*maxn];
int n,m,ch,x,y,d;
void initial()
{
    rt=np=0;
    for(int i=1;i<=2*n;i++) setv[i]=-1;
    memset(lc,0,sizeof(lc));
    memset(rc,0,sizeof(rc));
}
void pushup(int now)
{
    minv[now]=min(minv[lc[now]],minv[rc[now]]);
    maxv[now]=max(maxv[lc[now]],maxv[rc[now]]);
    sum[now]=sum[lc[now]]+sum[rc[now]];
}
void pushdowncha(int now,int L,int R)
{
    if(setv[now]>=0)
    {
        int m=(L+R)>>1;
        add[lc[now]]=0;
        add[rc[now]]=0;
        maxv[lc[now]]=minv[lc[now]]=maxv[rc[now]]=minv[rc[now]]=setv[now];
        sum[lc[now]]=(m-L+1)*setv[now];
        sum[rc[now]]=(R-m)*setv[now];
        setv[lc[now]]=setv[now];
        setv[rc[now]]=setv[now];
        setv[now]=-1;
    }
}
void pushdownad(int now,int L,int R)
{
    if(add[now]>0)
    {
        int m=(L+R)>>1;
        sum[lc[now]]+=(m-L+1)*add[now];
        sum[rc[now]]+=(R-m)*add[now];
        maxv[lc[now]]+=add[now];
        add[lc[now]]+=add[now];
        maxv[rc[now]]+=add[now];
        add[rc[now]]+=add[now];

        minv[lc[now]]+=add[now];
        minv[rc[now]]+=add[now];
        add[now]=0;
    }
}
void build(int &now,int L,int R)
{
    now=++np;
    if(L==R)
    {
        maxv[now]=minv[now]=sum[now]=a[L];
        return;
    }
    int m=(L+R)>>1;
    build(lc[now],L,m);
    build(rc[now],m+1,R);
    pushup(now);
}
void update(int now,int L,int R,int i,int j,int d)
{
    if(L>=i && R<=j)
    {
        minv[now]+=d;
        maxv[now]+=d;
        sum[now]+=(R-L+1)*d;
        add[now]+=d;
        return;
    }
    pushdowncha(now,L,R);
    pushdownad(now,L,R);

    int m=(L+R)>>1;
    if(j<=m) update(lc[now],L,m,i,j,d);
    else if(i>m) update(rc[now],m+1,R,i,j,d);
    else
    {
        update(lc[now],L,m,i,m,d);
        update(rc[now],m+1,R,m+1,j,d);
    }
    pushup(now);
}
void change(int now,int L,int R,int i,int j,int d)
{
    if(L>=i && R<=j)
    {
        sum[now]=(R-L+1)*d;
        maxv[now]=minv[now]=d;
        setv[now]=d;
        add[now]=0;//保证了若刷值后仍有add,则必定加值在刷值之后 
        return;
    }
    pushdowncha(now,L,R);
    pushdownad(now,L,R);
    int m=(R+L)>>1;
    if(j<=m) change(lc[now],L,m,i,j,d);
    else if(i>m) change(rc[now],m+1,R,i,j,d);
    else 
    {
        change(lc[now],L,m,i,m,d);
        change(rc[now],m+1,R,m+1,j,d);
    }
    pushup(now);
}
int querysum(int now,int L,int R,int i,int j)
{
    if(L>=i && R<=j) return sum[now];
    pushdowncha(now,L,R);
    pushdownad(now,L,R);

    int m=(L+R)>>1;
    if(j<=m) return querysum(lc[now],L,m,i,j);
    else if(i>m) return querysum(rc[now],m+1,R,i,j);
    else
    {
        return querysum(lc[now],L,m,i,m)+querysum(rc[now],m+1,R,m+1,j);
    }
}
int querymin(int now,int L,int R,int i,int j)
{
    if(L>=i && R<=j) return minv[now];
    pushdowncha(now,L,R);
    pushdownad(now,L,R);

    int m=(L+R)>>1;
    if(j<=m) return querymin(lc[now],L,m,i,j);
    else if(i>m) return querymin(rc[now],m+1,R,i,j);
    else
    {
        return min(querymin(lc[now],L,m,i,m),querymin(rc[now],m+1,R,m+1,j));
    }
}
int querymax(int now,int L,int R,int i,int j)
{
    if(L>=i && R<=j) return maxv[now];
    pushdowncha(now,L,R);
    pushdownad(now,L,R);

    int m=(L+R)>>1;
    if(j<=m) return querymax(lc[now],L,m,i,j);
    else if(i>m) return querymax(rc[now],m+1,R,i,j);
    else
    {
        return max(querymax(lc[now],L,m,i,m),querymax(rc[now],m+1,R,m+1,j));
    }
}
void dfs(int now,int L,int R)
{
    if(now)
    {
        printf("%d %d %d\n",L,R,sum[now]);
        int m=(L+R)>>1;
        dfs(lc[now],L,m);
        dfs(rc[now],m+1,R);
    }
}
int main()
{
//  freopen("in.txt","r",stdin);
//  freopen("out.txt","w",stdout);
    scanf("%d%d",&n,&m);
    initial();
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    build(rt=0,1,n);
    while(m--)
    {
        scanf("%d%d%d",&ch,&x,&y);
        if(ch==1)
        {
            scanf("%d",&d);
            update(rt,1,n,x,y,d);
        }
        else if(ch==2)
        {
            scanf("%d",&d);
            change(rt,1,n,x,y,d);
        }
        else 
        {
            printf("%d %d %d\n",querysum(rt,1,n,x,y),querymin(rt,1,n,x,y),querymax(rt,1,n,x,y));
        }
//      cout<<m<<endl;
//      dfs(rt,1,n);
//      cout<<endl;
    }


    return 0;
}

讲道理这题真的坑:
1.关于lazy数组add[i]和setv[i]的判断顺序和是否清零的问题,先判断setv[i]是否>=0,然后进行下放,注意此时add[lc[now]]和add[rc[now]]要清零,add[now]不用,因为在之前change()函数里面已经排除了先加再清零的可能性,所以此处add[now]不变,若它有值则下一步继续修改值。
2.v可取到0,所以在初始化setv[i]的时候记得把值赋为-1。




上面的代码中基本包含了线段树的基本运算:

//1. 初始化:
void initial(){rt=np=0;memset(rc,0,sizeof(rc));memset(lc,0,sizeof(lc));}

//2. 上传操作(后序进行):
void pushup(int now)
{
    maxv[now]=max(maxv[lc[now]],maxv[rc[now]]);
}

//3.下传操作(先序进行):
void pushdown(int now)
{
    maxv[lc[now]]+=add[now];
    add[lc[now]]+=add[now];
    maxv[rc[now]]+=add[now];
    add[rc[now]]+=add[now];
    add[now]=0;
}

//4.建立(类似二叉树):
void build(int &now,int L,int R)
{
    now=++np;
    if(L==R)
    {
        maxv[now]=a[L];
        return;
    }
    int m=(L+R)>>1;
    build(lc[now],L,m);
    build(rc[now],m+1,R);
    pushup(now);
}

//5.对区间进行操作:
//例如将区间i~j中的所以元素值加d
void update(int now,int L,int R,int i,int j,int d)//把区间[i...j]的值加d 
{
    if(L>=i && R<=j)
    {
        maxv[now]+=d;
        add[now]+=d;
        return;
    } 

    pushdown(now);
    int m=(L+R)>>1;
    if(j<=m) update(lc[now],L,m,i,j,d);
    else if(i>m) update(rc[now],m+1,R,i,j,d);
    else 
    {
        update(lc[now],L,m,i,m,d);
        update(rc[now],m+1,R,m+1,j,d);
    }
    pushup(now);
}

//6.查找i~j中的最大元素:
int query(int now,int L,int R,int i,int j)
{
    if(L>=i && R<=j)
    {
        return maxv[now];
    }

    pushdown(now);
    int m=(L+R)>>1;
    if(j<=m) return query(lc[now],L,m,i,j);
    else if(i>m) return query(rc[now],m+1,R,i,j);
    else
    {
        return max(query(lc[now],L,m,i,m),query(rc[now],m+1,R,m+1,j));
    }
}
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值