题解 P3372 【【模板】线段树 1】

题目

刚刚学了树状数组的区间加法和区间求和操作,就用来水掉这题了

本篇适合学会树状数组的人群

前置芝士:

普通树状数组

差分树状数组


【分析】

学过树状数组的人都知道,我们对于一个数组,进行处理后,就可以在 \(O(\log n)\) 的时间内进行单点修改和区间求和

假设对于数组 \(a_n\) ,我们用树状数组的方法处理后,就可以单点修改任意 \(a_i\) 的值,或者求 \(\displaystyle \sum_{i=l}^r a_n\)

如果我们差分一下 \(a_n\) ,设 \(c_n=a_n-a_{n-1}\) ,那就可以使得树状数组区间修改和单点查询 \(\displaystyle a_n=\sum_{i=1}^n c_i\)


在差分的基础下,我们如果需要求区间和,则先相同地:

\(\displaystyle \sum_{i=l}^ra_i=\sum_{i=1}^ra_i-\sum_{i=1}^{l-1}a_i\)

所以,在此基础上,我们只需要求解 \(\displaystyle \sum_{i=1}^k a_i\) 即可求解区间求和问题了

我们变型这个式子:

\(\displaystyle \sum_{i=1}^k a_i=\sum_{i=1}^k\sum_{j=1}^ic_i=\sum_{i=1}^k(k+1-i)c_i\)

这玩意儿没办法直接求,所以我们把它分开来:

\(\displaystyle \sum_{i=1}^k a_i=(k+1)\sum_{i=1}^k c_i-\sum_{i=1}^k ic_i\)

这样,就能分开来,用树状数组实现求和了。

因此,我们只需要实现 \(c_n\) 和 \(nc_n\) 的树状数组,即可以实现区间求和

即前者: \(c_n\) 差分树状数组的区间和,乘上 \((k+1)\) ,减去后者: \(nc_n\) 差分树状数组的区间和


当然,我们还应考虑修改对吧

假设修改区间 \([l,r]\) 都加上 \(a\)

首先, \(c_n\) 的修改肯定是没问题的:\(c_l+=a,c_{r+1}+=-a\)

分解为差分树状数组单点修改 \(c_l\) 和 \(c_{r+1}\)

而我们很清楚,假设 \(c_i\) 转变为 \((c_i+a)\)

则有 \(ic_i\) 转变为 \(i(c_i+a)=ic_i+ia\)

也就是说,\(ic_i+=ia\) ,而 \(ic_i\) 的增加对本身累加了 \(ic_i\) 的贡献都是增加 \(ia\) 的

所以只要在修改 \(c_i+=a\) 的时候,顺便修改 \(ic_i+=ia\) 即可


那本蒟蒻就放 我码风极丑的 代码了:

#include<cstdio>
using namespace std;
#define f(a,b,c,d) for(register int a=b,c=d;a<=c;a++)
#define g(a,b,c,d) for(register int a=b,c=d;a>=c;a--)
#define LOCAL
typedef int i32;
typedef unsigned int u32;
typedef long long int i64;
typedef unsigned long long int u64;
const i32 MAXN=1e5+10;
typedef i64 ar[MAXN];
//一堆条件反射的定义

namespace HABIT{//读入输出优化而已,直接跳过看正文即可
    #ifdef LOCAL
        inline char gc() { return getchar(); }
    #else
        inline char gc() {
            static char s[1<<20|1]={0},*p1=s,*p2=s;
            return (p1==p2)&&(p2=(p1=s)+fread(s,1,1<<20,stdin),p1==p2)?EOF:*(p1++);
        }
    #endif
    inline i32 read(){
        register i32 ans=0;register char c=gc();register bool neg=0;
        while(c<48||c>57) neg^=!(c^'-'),c=gc();
        while(c>=48&&c<=57) ans=(ans<<3)+(ans<<1)+(c^48),c=gc();
        return neg?-ans:ans;
    }

    char Output_Ans[1<<20|1],*Output_Cur=Output_Ans;
    inline void output() { Output_Cur-=fwrite(Output_Ans,1,Output_Cur-Output_Ans,stdout); }
    inline void print(char c){ if(Output_Cur-Output_Ans+1>>20) output(); *(Output_Cur++)=c; }
    inline void print(char *s) { while(*s) print( *(s++) ); }
    inline void print(u64 x){
        if(!x) { print('0'); return ; }
        char buf[30]={0},*p=buf+28;
        while(x) *(p--)=x%10+48,x/=10;
        print(p+1);
    }
    inline void print(i64 x){ if(x<0) print('-'),x=-x; print( (u64)x ); }
}
using namespace HABIT;

//正文开始
i32 d_N,d_M;
ar ar_d_iC,ar_d_C;

inline void add(i32 pos,i64 a){
    const i32 ia=a*pos;
    for(i32 i=pos;i<=d_N;i+=(i&(-i)))//树状数组基操,不停加 lowbit(i)
        ar_d_iC[i]+=ia,ar_d_C[i]+=a;//ici+=ia,ci+=a
}
inline i64 sum(i32 pos){//(pos+1)*求和 ci -求和 ici
    i64 d_Ans=0;
    for(i32 i=pos;i;i-=(i&(-i))) d_Ans+=ar_d_C[i];
    d_Ans*=(pos+1);
    for(i32 i=pos;i;i-=(i&(-i))) d_Ans-=ar_d_iC[i];
    return d_Ans;
}

inline void update(i32 h,i32 t,i64 a) { add(h,a); add(t+1,-a); }
inline i64 query(i64 h,i64 t) { return sum(t)-sum(h-1); }

int main(){
    d_N=read(),d_M=read();
    f(i,1,I,d_N) update(i,i,read());
    f(i,1,I,d_M)
        if(read()==1){
            i32 h=read(),t=read();
            update(h,t,read());
        }
        else{
            i32 h=read(),t=read();
            print(query(h,t)),print('\n');
        }
    output();
    return 0;
}

最后安利以下 本蒟蒻的博客

上一篇:swoole 安装 搭建tcp服务器和websocket


下一篇:luogu P5488 差分与前缀和 FFT