「Luogu」P3372 线段树模板

Posted locaetric

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了「Luogu」P3372 线段树模板相关的知识,希望对你有一定的参考价值。

题意:原题在这
 
给定一个数列,进行两种操作:
 
   1.将某个区间每一个数+x
 
   2.求区间和

 

思路:
 
只有一点:注意开long long
 
代码:
 
#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 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;
}

 

辣鸡蒟蒻再附一个动态开点做法,虽然在这个题上跑的稍微慢了一点

 

CAUTION!!!!!!!!!!!!
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;
}

 

以上是关于「Luogu」P3372 线段树模板的主要内容,如果未能解决你的问题,请参考以下文章

Luogu P3372 模板线段树 1

luogu P3372 线段树1模板

模板线段树(luogu P3372)

洛谷P3372线段树模板1——线段树

luogu P3372 模板线段树 1

luogu P3372ybtoj线段树课堂过关例题2区间查改 &模板线段树 1