树状数组的扩展

馋奶兔 提交于 2019-12-04 07:01:57

一、基础树状数组

(一)前缀和实现(单点修改,区间查询)

inline int lowbit(int x){return x&-x;}
inline void change(int x,int k){
    while(x<=n){
        tree[x]+=k;
        x+=lowbit(x);
    }
}
inline ll query(int x){
    ll ans=0;
    while(x){
        ans+=tree[x];
        x-=lowbit(x);
    }
    return ans;
}

(二)差分实现(区间修改,单点查询)

区间修改,单点查询在修改时维护一个差分即可

change(x,k);
change(y+1,-k);

二、区间树状数组(区间修改,区间查询)

考虑\([1,r]\)的区间和

\(tree[i]=a[i]-a[i-1]\),我们有\(a[i]=\sum_\left.k=1\right.^i tree[k]\)


\[ \sum_\left.i=1\right.^ra[i]=\sum_\left.i=1\right.^r\sum_\left.k=1\right.^i tree[k]=\sum_\left.i=1\right.^\left.r\right.tree[i]*(r-i+1)=r*\sum_\left.i=1\right.^rtree[i]-\sum_\left.i=1\right.^\left.r\right.tree[i]*(i-1) \]
\(cf[i]=tree[i]*(i-1)\),则我们要求
\[ \sum_\left.i=1\right.^ra[i]=r*\sum_\left.i=1\right.^rtree[i]-\sum_\left.i=1\right.^\left.r\right.cf[i] \]

//初值
for(int i=1;i<=n;i++){
    scanf("%lld",&x);
    change(tree,i,x-now);
    change(cf,i,(i-1)*(x-now));
    now=x;
} 
//区间修改 区间为[x,y]
change(tree,x,k);
change(tree,y+1,-k);
change(cf,x,k*(x-1));
change(cf,y+1,-k*y);
//区间查询 区间为[x,y]
ll sum1=y*query(tree,y)-(x-1)*query(tree,x-1);
ll sum2=query(cf,y)-query(cf,x-1);
printf("%lld\n",sum1-sum2);

Update一种更优秀的写法

inline int lowbit(int x){return x&-x;}
void change(int x,ll k){
    ll p=x;
    while(x<=n){
        tree[x]+=k;
        cf[x]+=(p-1)*k;
        x+=lowbit(x);           
    }
}
ll query(int x){
    ll ans=0,p=x;
    while(x){
        ans+=p*tree[x]-cf[x];
        x-=lowbit(x);
    }
    return ans;
}
//修改
change(x,k);
change(y+1,-k);
//查询
printf("%lld\n",query(y)-query(x-1));

三、高维树状数组

(一)前置技能

更多可参见 https://www.cnblogs.com/Miracevin/p/9778266.html

前缀和及差分可利用容斥解决,不再详细展开

高维前缀和就是求一个集合子集状态的和

二维前缀和

for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
         sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+a[i][j];

for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
        a[i][j]+=a[i][j-1];
for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
        a[i][j]+=a[i-1][j];

即为前缀和逆过来

高维二元素前缀和(w表示最高维度)

for(int i=0;i<w;i++)
    for(int j=0;j<(1<<w);j++)
        if(j&(1<<i))f[j]+=f[j^(1<<i)];

(二)基础二维树状数组

单点修改,区间查询

inline int lowbit(int x){return x&(-x);}
void change(int x,int y,ll k){
    for(int i=x;i<=n;i+=lowbit(i))
    for(int j=y;j<=m;j+=lowbit(j))
        tree[i][j]+=k;
}
ll query(int x,int y){
    ll ans=0;
    for(int i=x;i>=1;i-=lowbit(i))
    for(int j=y;j>=1;j-=lowbit(j))
        ans+=tree[i][j];
    return ans;
}
//查询
query(x2,y2)-query(x_1-1,y2)-query(x2,y_1-1)+query(x_1-1,y_1-1)

(三)区间二维树状数组

类似一维情况,可得到
\[ \sum_\left.i=1\right.^x\sum_\left.j=1\right.^ya[i][j]=x*y\sum_\left.i=1\right.^x\sum_\left.j=1\right.^ytree[i][j]-y*\sum_\left.i=1\right.^x\sum_\left.j=1\right.^ytree[i][j]*(i-1)\\-x*\sum_\left.i=1\right.^x\sum_\left.j=1\right.^ytree[i][j]*(j-1)+\sum_\left.i=1\right.^x\sum_\left.j=1\right.^ytree[i][j]*(i-1)*(j-1) \]
类似于容斥,可推广到n维情况

代码CSP之后再更新(维护4个数组更新,查询时带上系数)

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!