P3372 模板线段树 1

Posted five20

tags:

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

篇首的话:

  马上就又要考试了,复习一下板子,以前学线段树的时候还很懵,现在回过头来补一下档。。。

 

题目描述

如题,已知一个数列,你需要进行下面两种操作:

1.将某区间每一个数加上x

2.求出某区间每一个数的和

输入输出格式

输入格式:

第一行包含两个整数N、M,分别表示该数列数字的个数和操作的总个数。

第二行包含N个用空格分隔的整数,其中第i个数字表示数列第i项的初始值。

接下来M行每行包含3或4个整数,表示一个操作,具体如下:

操作1: 格式:1 x y k 含义:将区间[x,y]内每个数加上k

操作2: 格式:2 x y 含义:输出区间[x,y]内每个数的和

输出格式:

输出包含若干行整数,即为所有操作2的结果。

输入输出样例

输入样例#1: 
5 5
1 5 4 2 3
2 2 4
1 2 3 2
2 3 4
1 1 5 1
2 1 4
输出样例#1: 
11
8
20

说明

时空限制:1000ms,128M

数据规模:

对于30%的数据:N<=8,M<=10

对于70%的数据:N<=1000,M<=10000

对于100%的数据:N<=100000,M<=100000

(数据已经过加强^_^,保证在int64/long long数据范围内)

样例说明:

 

Solution:

  本题是最基础的线段树模板——区间加减、区间求和。

  我这里提供三种方法:1、线段树  2、树状数组  3、分块

  简单讲一下线段树(鉴于线段树网上的讲解已经十分清楚详细,我就随便将点理解):

  线段树是一种基于分治思想的二叉树结构,用于区间上进行信息统计。

  线段树的每个节点维护的是一个区间; 线段树具有唯一的根节点表示整个统计范围($[1,n]$); 线段树的每个叶子节点都表示一个长度为1的单元区间($[x,x]$); 对于每个内部节点$[l,r]$,它的左子节点为$[l,mid]$,右子节点为$[mid+1,r]$,其中$mid=(l+r)/2$。

  建树:

  根节点编号为$1$,编号为x的左子节点为$x*2$,右儿子节点为$x*2+1$。依此,递归建树,同时在递归过程中预处理所需信息(以本题为例,在递归建树时我们可以维护出每个子区间的和)。容易发现,当有N个叶子节点时,该树总共的节点数最多为$N+N/2+N/4+…+2+1=2N-1$(即一颗满二叉树)。于是保存线段树的数组长度至少为$4N$,才能保证不会越界。

  区间修改:

  从上往下递归,判断区间是否包含,若当前的区间完全被$[l,r]$覆盖则直接更改,若不覆盖则继续递归左右儿子节点,这里我的代码实现时用到了延迟标记(add[rt]表示编号rt的区间所增加的值),顾名思义就是先不将修改的值下放到子区间中而是在当前区间要被修改或被查询时再进行修改(详见代码)。最后记得回溯时维护所需信息(本题中的区间和)。

  区间查询:

  与区间修改类似,判断区间是否包含,若被完全覆盖直接回溯并返回当前区间的信息(本题中的区间和),否则就继续递归左右儿子节点,当然也得记得要在找到完全覆盖区间之前下放延迟标记。最后返回所需的区间信息就ok了(本题中的区间和)。

  讲的不够清楚,不理解可以看代码或自行百度~~手动滑稽~~

线段树代码:

 

#include<bits/stdc++.h>
#define il inline
#define ll long long
#define debug printf("%d %s\\n",__LINE__,__FUNCTION__)
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
using namespace std;
const int N=100005;
ll n,m,sum[N<<2],add[N<<2];
il ll gi()
{
    ll a=0;char x=getchar();bool f=0;
    while((x<\'0\'||x>\'9\')&&x!=\'-\')x=getchar();
    if(x==\'-\')x=getchar(),f=1;
    while(x>=\'0\'&&x<=\'9\')a=a*10+x-48,x=getchar();
    return f?-a:a;
}
il void pushup(int rt){sum[rt]=sum[rt<<1]+sum[rt<<1|1];}
il void pushdown(int rt,int m)
{
    if(add[rt]){
        add[rt<<1]+=add[rt];
        add[rt<<1|1]+=add[rt];
        sum[rt<<1]+=add[rt]*(m-(m>>1));
        sum[rt<<1|1]+=add[rt]*(m>>1);
        add[rt]=0;
    }
}
il void build(int l,int r,int rt)
{
    add[rt]=0;
    if(l==r){sum[rt]=gi();return;}
    int m=l+r>>1;
    build(lson),build(rson);
    pushup(rt);
}
il void update(int L,int R,ll c,int l,int r,int rt)
{
    if(L<=l&&R>=r){
        add[rt]+=c;
        sum[rt]+=(ll)c*(r-l+1);
        return ;
    }
    pushdown(rt,r-l+1);
    int m=l+r>>1;
    if(L<=m)update(L,R,c,lson);
    if(m<R)update(L,R,c,rson);
    pushup(rt);
}
il ll query(int L,int R,int l,int r,int rt)
{
    if(L<=l&&R>=r)return sum[rt];
    pushdown(rt,r-l+1);
    int m=l+r>>1;
    ll ret=0;
    if(L<=m)ret+=query(L,R,lson);
    if(m<R)ret+=query(L,R,rson);
    return ret;
}
int main()
{
    n=gi(),m=gi();
    build(1,n,1);
    ll u,x,y,k;
    while(m--){
        u=gi();
        if(u==1){
            x=gi(),y=gi(),k=gi();
            update(x,y,k,1,n,1);
        }
        else {
            x=gi(),y=gi();
            printf("%lld\\n",query(x,y,1,n,1));
        }
    }
    return 0;
}

 

 

  树状数组思路:

  再谈谈树状数组,由于这道题只是区间修改和区间查询,于是我们可以用代码量超少且常数更小的树状数组去做啦,大致就是用差分约束存数并修改,再用前缀和+辅助数组来求和,我记得以前写过树状数组的详解,可以去看一下,所以这里就不解释了。(其实是我有点忘了~~!)

树状数组代码:

 

#include<bits/stdc++.h>
#define maxn 200005
using namespace std;
#define ll long long
ll  a[maxn],b[maxn],c[maxn],n,m;
inline ll getint()
{
    ll a=0;char x=getchar();bool f=0;
    while((x<\'0\'||x>\'9\')&&x!=\'-\')x=getchar();
    if(x==\'-\')f=1,x=getchar();
    while(x>=\'0\'&&x<=\'9\'){a=a*10+x-\'0\';x=getchar();}
    return f?-a:a;
}
void update(ll *x,ll k,ll num)
{
    while(k<=n)
    {
        x[k]+=num;
        k+=k&-k;
    }
}
ll read(ll *x,ll k)
{
    ll sum=0;
    while(k){sum+=x[k];k-=k&-k;}
    return sum;
}
int main()
{
    n=getint(),m=getint();
    for(ll i=1;i<=n;i++){a[i]=getint();update(b,i,a[i]-a[i-1]);update(c,i,(i-1)*(a[i]-a[i-1]));}
    while(m--)
    {
        ll x,y,z=getint(),q;
        if(z==2){x=getint();y=getint();printf("%lld\\n",y*read(b,y)-read(c,y)-(x-1)*read(b,x-1)+read(c,x-1));}
        else {x=getint();y=getint();q=getint();update(b,x,q);update(b,y+1,-q);update(c,x,q*(x-1));update(c,y+1,-q*y);}
    }
    return 0;
}

 

 

   分块思路:

   然后扯下炒鸡优美的暴力——分块。思路比较简单,就是将整个区间分为$\\sqrt n$块,统计出每块内的$sum$,然后对于修改则用思想“大段维护、局部朴素”,也就是对于区间中间整体包含的直接累加(更改则打标记,用$add$数组记录某块的增量),两边多余的则暴力累加(更改也是暴力,注意这里更改的是原数,并不影响开始每块的$sum$值),具体实现看代码体会(话说分块比线段树还跑的快,可见洛谷数据也是有点水~~)。

分块代码:

 

#include<bits/stdc++.h>
#define il inline
#define ll long long
#define debug printf("%d %s\\n",__LINE__,__FUNCTION__)
using namespace std;
const int N=100005;
ll a[N],sum[N],add[N];
int pos[N],n,m;
struct data{
    int l,r;
}t[N];
il ll gi()
{
    ll a=0;char x=getchar();bool f=0;
    while((x<\'0\'||x>\'9\')&&x!=\'-\')x=getchar();
    if(x==\'-\')x=getchar(),f=1;
    while(x>=\'0\'&&x<=\'9\')a=a*10+x-48,x=getchar();
    return f?-a:a;
}
il void change(int l,int r,ll d){
    int p=pos[l],q=pos[r];
    if(p==q){
        for(int i=l;i<=r;i++)a[i]+=d;
        sum[p]+=d*(r-l+1);
    }
    else {
        for(int i=p+1;i<=q-1;i++)add[i]+=d;
        for(int i=l;i<=t[p].r;i++)a[i]+=d;
        sum[p]+=d*(t[p].r-l+1);
        for(int i=t[q].l;i<=r;i++)a[i]+=d;
        sum[q]+=d*(r-t[q].l+1);
    }
}
il ll ask(int l,int r){
    int p=pos[l],q=pos[r];
    ll ans=0;
    if(p==q){
        for(int i=l;i<=r;i++)ans+=a[i];
        ans+=add[p]*(r-l+1);
        return ans;
    }
    for(int i=p+1;i<=q-1;i++)ans+=sum[i]+add[i]*(t[i].r-t[i].l+1);
    for(int i=l;i<=t[p].r;i++)ans+=a[i];
    ans+=add[p]*(t[p].r-l+1);
    for(int i=t[q].l;i<=r;i++)ans+=a[i];
    ans+=add[q]*(r-t[q].l+1);
    return ans;
}
int main()
{
    n=gi(),m=gi();
    int s=int(sqrt(n));
    for(int i=1;i<=n;i++){
        a[i]=gi();
        t[i].l=(i-1)*s+1;t[i].r=i*s;
    }
    if(t[s].r<n)s++,t[s].l=t[s-1].r+1,t[s].r=n;
    for(int i=1;i<=s;i++)
        for(int j=t[i].l;j<=t[i].r;j++)pos[j]=i,sum[i]+=a[j];
    int u,v,w,z;
    while(m--){
        u=gi();
        if(u==1){
            v=gi(),w=gi(),z=gi();
            change(v,w,z);
        }
        else {v=gi(),w=gi(),printf("%lld\\n",ask(v,w));}
    }
    return 0;
}

 

 

 

 

 

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

P3372 模板线段树 1

P3372 模板线段树 1(区间修改区间查询)(树状数组)

P3372 模板线段树 1

线段树模板1 [Luogu P3372]

洛谷P3372 模板线段树 1

P3372 模板线段树 1