「模板」线段树静态开点(单点+区间修改)、动态开点

相关讲解资料:

树状数组:https://blog.csdn.net/qq_34374664/article/details/52787481 (线段树预备)

线段树讲解:

    初学版:https://blog.csdn.net/zearot/article/details/52280189

    进阶完整版:https://www.cnblogs.com/AC-King/p/7789013.html

代码:

完整注释模板一张,参(chao)考(xi)楼上的博客

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<queue>
#include<algorithm>
using namespace std;
typedef long long ll;

#define mid  (l+r)/2
#define lson (pos<<1)
#define rson ((pos<<1)|1)
#define maxn 100007 //元素个数

ll n,m;
ll root=1;
ll arr[maxn];
ll Lazy[maxn<<2];//区间增加的lazy标记
/*其目的是:
            为防止修改区间总结点对每个子节点都要进行修改,导致复杂度爆炸
            暂时记录一下这个区间总结点的所有子树都“待修改”
            如果用到下面的子节点就修改,下推lazy标志,用不到就不管
            以此来减少复杂度
*/
ll sum[maxn<<2];//线段树求和最多分成4个子区间

void PushUp(long long pos)//暂时写成求和函数,可以自由变换
{
    sum[pos]=sum[lson]+sum[rson];
    //用数组表示二叉树:假设某个节点的编号为v,那么它的左子节点编号为2*v,右子节点编号为2*v+1,规定根节点为1
    //通常2*v写成v<<1 , 2*v+1写成v<<1|1;
}

void PushDown(long long pos,long long l,long long r)//区间查询用
{
    //l,r为左子树,右子树的数字区间
    if(Lazy[pos])
    {
        //修改子节点的增加数
        Lazy[lson]+=Lazy[pos];
        Lazy[rson]+=Lazy[pos];
        //修改子节点区间的sum
        sum[lson]+=Lazy[pos]*(mid-l+1);
        sum[rson]+=Lazy[pos]*(r-(mid+1)+1);
        //清除本节点标记
        Lazy[pos]=0;
    }
}

void Build(long long l,long long r,long long pos)//[l,r]表示当前节点区间,pos表示当前节点的实际存储位置
{
    if(l==r)//如果到达儿子节点,存储并返回
    {
        sum[pos]=arr[l];
        return;
    }
    Build(l,mid,pos<<1);
    Build(mid+1,r,pos<<1|1);
    PushUp(pos);
}

void UpPoint(long long pos,long long l,long long r,long long L,long long C)//对单点修改
{
    //L表示要修改的点编号,[l,r]表示当前区间,pos是当前节点编号;
    if(l==r)//到达儿子节点之后就修改
    {
        sum[pos]+=C;
        return;
    }
    //根据条件判断往左子树调用还是往右
    if(L<=mid) UpPoint(lson,l,mid,L,C);
    else UpPoint(rson,mid+1,r,L,C);
    PushUp(pos);//子节点更新之后本节点也需要更新;
}

void UpZone(long long pos,long long l,long long r,long long L,long long R,long long C)//对整个区间进行修改
{
    //L,R表示操作区间 , l,r表示当前节点区间 , pos表示当前节点编号
    if(L<=l && R>=r)//节点区间在操作区间之内,直接返回
    {
        sum[pos]+=C*(r-l+1);//这个点需要加上区间长度*C
        Lazy[pos]+=C;//用Lazy标记,表示本区间的Sum正确,子区间的Sum仍需要根据Add调整
        return;
    }
    PushDown(pos,l,r);//下推标记
    if(L<=mid) UpZone(lson,l,mid,L,R,C);
    if(R>mid) UpZone(rson,mid+1,r,L,R,C);
    PushUp(pos);
}

ll Query(long long l,long long r,long long L,long long R,long long pos)
{
    //L,R表示操作区间 , l,r表示当前节点区间 , pos表示当前节点编号
    if(L<=l && R>=r)//节点区间在操作区间之内,直接返回
    {
        return sum[pos];
    }
    PushDown(pos,l,r);//下推标记,否则sum可能不正确

    //统计答案
    long long ans=0;
    if(L<=mid) ans+=Query(l,mid,L,R,lson);
    if(R>mid) ans+=Query(mid+1,r,L,R,rson);
    PushUp(pos);
    return ans;
}

int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        long long tmp;
        cin>>tmp;
        UpZone(root,1,n,i,i,tmp);
    }
    for(int j=1;j<=m;j++)
    {
        long long a,b,c,d;
        cin>>a;
        if(a==1)
        {
            cin>>b>>c>>d;
            UpZone(root,1,n,b,c,d);
        }
        else
        {
            cin>>b>>c;
            cout<<Query(1,n,b,c,root)<<endl;
        }
    }
    return 0;
}

下面是动态开点的模板:

1. 不能define lson,rson,也不能用pos<<1和pos<<1|1,否则就失去了“动态开点”的意义
2. Get_Son和UpZone要&引用
3. 尽量开long long,也好调

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<queue>
#include<algorithm>
using namespace std;
typedef long long ll;

//!!!!!!!!!
//Get_Son和UpZone要&引用
//不能define  lson,rson,也不能用pos<<1和pos<<1|1
//!!!!!!!!!

#define mid  (l+r)/2
#define maxn 1000007 //元素个数

ll n,m;
ll root=1,cnt=1;
ll lson[maxn],rson[maxn];
ll Lazy[maxn<<2];//区间增加的lazy标记
/*其目的是:
            为防止修改区间总结点对每个子节点都要进行修改,导致复杂度爆炸
            暂时记录一下这个区间总结点的所有子树都“待修改”
            如果用到下面的子节点就修改,下推lazy标志,用不到就不管
            以此来减少复杂度
*/
ll sum[maxn<<2];//线段树求和最多分成4个子区间

ll Get_Son(long long &pos)
{
    if(pos==0) pos=++cnt;
    return pos;
}

void PushUp(long long pos)
{
    sum[pos]=sum[lson[pos]]+sum[rson[pos]];
    //用数组表示二叉树:假设某个节点的编号为v,那么它的左子节点编号为2*v,右子节点编号为2*v+1,规定根节点为1
    //通常2*v写成v<<1 , 2*v+1写成v<<1|1;
}

void PushDown(long long pos,long long l,long long r)//区间查询用
{
    //l,r为左子树,右子树的数字区间

    // if(Lazy[pos]==0) return;
    // if(r-l<=1) return;
    // if(pos<<1!=0)
    // {
    //     pos<<1=++cnt;
    //     sum[pos<<1]+=(mid-l+1)*Lazy[pos];
    //     Lazy[pos<<1]+=Lazy[pos];
    // }
    // if(rson[pos]!=0)
    // {
    //     rson[pos]=++cnt;
    //     sum[rson[pos]]+=(r-mid+1)*Lazy[pos];
    //     Lazy[rson[pos]]+=Lazy[pos];
    // }
    sum[Get_Son(lson[pos])]+=(mid-l+1)*Lazy[pos];
    sum[Get_Son(rson[pos])]+=(r-mid)*Lazy[pos];
    Lazy[lson[pos]]+=Lazy[pos];
    Lazy[rson[pos]]+=Lazy[pos];
    Lazy[pos]=0;
}

void UpZone(long long &pos,long long l,long long r,long long L,long long R,long long C)
{
    //L,R表示操作区间 , l,r表示当前节点区间 , pos表示当前节点编号
    if(pos==0) pos=++cnt;
    if(Lazy[pos]!=0) PushDown(pos,l,r);//下推标记

    if(L<=l && R>=r)//节点区间在操作区间之内,直接返回
    {
        sum[pos]+=(r-l+1)*C;//这个点需要加上区间长度*C
        Lazy[pos]+=C;//用Lazy标记,表示本区间的Sum正确,子区间的Sum仍需要根据Lazy调整
        return;
    }

    if(L<=mid) UpZone(lson[pos],l,mid,L,R,C);
    if(R>mid) UpZone(rson[pos],mid+1,r,L,R,C);
    PushUp(pos);
}

ll Query(long long pos,long long l,long long r,long long L,long long R)
{
    //L,R表示操作区间 , l,r表示当前节点区间 , pos表示当前节点编号
    if(pos==0) return 0;
    if(Lazy[pos]) PushDown(pos,l,r);//下推标记,否则sum可能不正确

    if(L<=l && R>=r)//节点区间在操作区间之内,直接返回
    {
        return sum[pos];
    }

    //统计答案
    long long ans=0;
    if(L<=mid) ans+=Query(lson[pos],l,mid,L,R);
    if(R>mid) ans+=Query(rson[pos],mid+1,r,L,R);
    PushUp(pos);
    return ans;
}

int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        int tmp;
        cin>>tmp;
        UpZone(root,1,n,i,i,tmp);
    }
    for(int j=1;j<=m;j++)
    {
        int a,b,c,d;
        cin>>a;
        if(a==1)
        {
            cin>>b>>c>>d;
            UpZone(root,1,n,b,c,d);
        }
        else
        {
            cin>>b>>c;
            cout<<Query(root,1,n,b,c)<<endl;
        }
    }
    return 0;
}

原文地址:https://www.cnblogs.com/LocaEtric/p/9614245.html

时间: 2024-08-12 05:44:48

「模板」线段树静态开点(单点+区间修改)、动态开点的相关文章

「模板」 线段树——区间乘 &amp;&amp; 区间加 &amp;&amp; 区间求和

「模板」 线段树--区间乘 && 区间加 && 区间求和 <题目链接> 原来的代码太恶心了,重贴一遍. #include <cstdio> int n,m; long long p; class SegmentTree { private: struct Node { int l,r; long long v,mul,add; Node *c[2]; Node(int l,int r):l(l),r(r),mul(1LL),add(0LL) { c[

Bzoj 3050: [Usaco2013 Jan]Seating(线段树裸题,然而区间修改标记下放和讨论Push_up很揪心)

题目链接 题意:开始有一个空白的区间,每次可能进行两个操作:A 将一个长度为p的区间加入一段连续空白的位置 L:一个区间恢复空白:要求出A不能进行的次数. 非常裸的线段树题目,用线段树统计最大的空白区间,每个节点需要记录当前区间的最长空白区间,从左端开始的最长空白区间,从右端开始的最长空白区间.Push_up的时候要讨论下,可以分别取[l,mid]和[mid+1,r]的最大空白区间,也可以用[l,mid]的从右端开始的最长空白区间+[mid+1,r]从左端开始的最大空白区间. 每次A的时候,就查

「ZJOI2019」线段树

传送门 Description 线段树的核心是懒标记,下面是一个带懒标记的线段树的伪代码,其中 tag 数组为懒标记: 其中函数\(Lson(Node)\)表示\(Node\)的左儿子,\(Rson(Node)\)表示\(Node\)的右儿子. 有一棵 \([1,n]\)上的线段树,编号为\(1\) .初始时什么标记都没有. 每次修改会把当前所有的线段树复制一份,然后对于这些线段树实行一次区间修改操作. 每次修改后线段树棵数翻倍,第 \(i\)次修改后,线段树共有 \(2^i\) 棵. 每次询问

线段树(二):区间修改

上一节介绍了点修改与区间查询的线段树,事实上,线段树还可以做得更多.本节讨论区间修改问题. 给出一个$n$个元素的数组$A_1,A_2,...,A_n$,你的任务是设计一个数据结构,支持以下两种操作: $Add(L,R,v)$:把$A_L,A_{L+1}, ..., A_R$的值全部增加$v$ $Query(L, R)$:计算子序列$A_L,A_{L+1},...,A_R$的元素和.最大值和最小值 点修改只会影响到$logn$个结点,但区间修改在最坏情况下会影响到树中的所有结点,比如,如果对整个

「模板」 树套树

「模板」 树套树 <题目链接> 线段树套 SBT. 有生以来写过的最长代码. 虽然能过,但我删除 SBT 点的时候没回收内存!写了就 RE! 先放上来吧,回收内存调出来了再修改qwq. #include <algorithm> #include <climits> #include <cstdio> using std::max; using std::min; const int MAXN=50010; int n,m; class SegmentTree

[Luogu 3701] 「伪模板」主席树

[Luogu 3701] 「伪模板」主席树 <题目链接> 这是一道网络流,不是主席树,不是什么数据结构,而是网络流. 题目背景及描述都非常的暴力,以至于 Capella 在做此题的过程中不禁感到生命流逝. S 向 byx 的树中的每一个人连有向边,手气君的树中的每一个人向 T 连有向边,边权为这个人的寿命.统计同一棵树中的膜法师数量 x.如果一个人是主席,那么边权要加上 x.(续得好啊) 然后,如果 byx 树中的一个点 i 能赢手气君树中的点 j,那么连 i->j,边权为 1. 跑最大

P3701 「伪模板」主席树

P3701 「伪模板」主席树 题目背景 byx和手气君都非常都非常喜欢种树.有一天,他们得到了两颗奇怪的树种,于是各自取了一颗回家种树,并约定几年后比一比谁种出来的树更加牛x. 题目描述 很快,这棵树就开花结果了.byx和手气君惊讶的发现,这是一棵主席树,树上长满了主席和主席的朋友们.这棵树上一共有五种人,主席(J),记者(HK),高人(W),女王(E)和膜法师(YYY).他们发现,他们的主席树上的人数相同,都为N. 研究发现,这五种人的输赢如上图所示(一样的人不能PK),箭头指向输的人.至于为

&#183;专题」 线段树

PKU暑期培训第一天,这次培训人很多,但是感觉打酱油的也不少,不知道大牛有多少. 第一天都是讲线段树的,课件的话和往常一样,没什么变化. 具体的话,讲了线段树和树状数组.线段树的话讲了单点更新,成段更新,扫描线已经离散化. 然后随便提了提树状数组.估计明天再讲一点点,然后接着是讲并查集,多串匹配什么的. 线段树的题目我做得很少,以前看过HH大神的模板,想模仿来着,其实也没什么理解. 重新理解一下线段树吧. 线段树的用途,主要是维护区间问题,比如区间的单点更新操作,成段更新,扫描线等等.当然还有一

·专题」 线段树

初学线段树(SegmentTree) 从HH大神那你学来的模板风格. 感觉确实相当飘逸. 现在做了4题..单点更新的, 想放上来,,以后慢慢整理!! ·单点更新」 ·刷题参考」 hdu1166 敌兵布阵 线段树第一题,单点更新第一题,可以作为线段树的模板, 思路:兵工厂作为数量n映射作为线段总长,更具输入进行单点的更新与查询,sub操作可以理解为add一个负数 Query操作:区间求和 update操作:单点的增减 1 #include<cstdio> 2 3 #define lson l,