zoukankan      html  css  js  c++  java
  • [模板]树状数组

    先膜黑科技讲义 - Magolor orz

    基础

    设lowbit(x)表示的是把x的二进制只留下最低一位的1,然后lowbit(x)=x&(-x) (我也不知道为什么)

    设c[x]表示从i往前一共lowbit(x)个数的和,那么x-lowbit(x)就是c[x]表示的范围的前一个数。

    然后可以得到c[x]=c[x-lowbit(x)+lowbit(x)>>1]+c[x-lowbit(x)+lowbit(x)>>1+lowbit(x)>>2]+c[x-lowbit(x)+lowbit(x)>>1+lowbit(x)>>2+lowbit(x)>>3]....+c[x-1]+a[x]。

    也就是说,如果将这个关系做成一个树,x的父亲就是x+lowbit(x)(观察一下上面的式子,很容易发现每一项都满足)

    要询问1到x的和的话,只要一直加c[x],然后让x-=lowbit(x)直到x=0就好了

    然后要修改一个点x的话,就一直往上修改它的父亲直到x>n就可以了。

    也就是说,最基本的树状数组支持的是单点修改和前缀和查询,然后前缀和可以很容易地做成区间和(端点减一减)。

    luogu3374

     1 #include<cstdio>
     2 #include<cstring>
     3 #include<algorithm>
     4 #define lowbit(x) ((x)&(-(x)))
     5 #define LL long long int
     6 const int maxn=500050;
     7 
     8 inline int rd(){
     9     int x=0;char c=getchar();int neg=1;
    10     while(c<'0'||c>'9'){if(c=='-') neg=-1;c=getchar();}
    11     while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
    12     return x*neg;
    13 }
    14 
    15 int N,M;
    16 LL c[maxn];
    17 
    18 inline void add(int x,int y){
    19     while(x&&x<=N) c[x]+=y,x+=lowbit(x);
    20 }
    21 inline LL query(int x){
    22     LL re=0;while(x) re+=c[x],x-=lowbit(x);return re;
    23 }
    24 
    25 int main(){
    26     int i,j,k;
    27     N=rd(),M=rd();
    28     for(i=1;i<=N;i++) add(i,rd());
    29     for(i=1;i<=M;i++){
    30         int a=rd(),b=rd(),c=rd();
    31         if(a==1) add(b,c);
    32         else printf("%lld
    ",query(c)-query(b-1));
    33     }
    34 }
    View Code

    也可以支持区间修改和单点查询,只要做一个差分,区间修改就变成了两个端点的修改,单点查询就变成了前缀和查询。

    luogu3368

     1 #include<cstdio>
     2 #include<cstring>
     3 #include<algorithm>
     4 #define lowbit(x) ((x)&(-(x)))
     5 #define LL long long int
     6 const int maxn=500050;
     7 
     8 inline int rd(){
     9     int x=0;char c=getchar();int neg=1;
    10     while(c<'0'||c>'9'){if(c=='-') neg=-1;c=getchar();}
    11     while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
    12     return x*neg;
    13 }
    14 
    15 int N,M;
    16 LL c[maxn];
    17 
    18 inline void add(int x,int y){
    19     while(x&&x<=N) c[x]+=y,x+=lowbit(x);
    20 }
    21 inline LL query(int x){
    22     LL re=0;while(x) re+=c[x],x-=lowbit(x);return re;
    23 }
    24 
    25 int main(){
    26     int i,j,k;
    27     N=rd(),M=rd();
    28     for(i=1,j=0;i<=N;i++){
    29         k=rd();add(i,k-j);j=k;
    30     };
    31     for(i=1;i<=M;i++){
    32         int a=rd(),b=rd();
    33         if(a==1){
    34             int c=rd(),d=rd();
    35             add(b,d);add(c+1,-d);
    36         }
    37         else printf("%lld
    ",query(b));
    38     }
    39 }
    View Code

    升级

    区间修改和区间查询也是可以支持的,然后我选择线段树......

    树状数组写起来真方便,suki

    $$前n项和sum_{i=1}^{n}{a[i]}=sum_{i=1}^{n}{sum_{j=1}^{i}{d[j]}}(差分数组)=sum_{i=1}^{n}{(n-i+1)d[i]}=(n+1)sum_{i=1}^{n}{d[i]}-sum_{i=1}^{n}{i*d[i]}$$

    这样的话,只要同时维护d[i]和i*d[i],就可以做到区间修改和区间查询了。

    luogu3372

     1 #include<cstdio>
     2 #include<cstring>
     3 #include<algorithm>
     4 #include<vector>
     5 #include<queue>
     6 #include<map>
     7 #include<cmath>
     8 #include<ctime>
     9 #include<set>
    10 #define pa pair<int,int>
    11 #define lowb(x) ((x)&(-(x)))
    12 #define REP(i,n0,n) for(i=n0;i<=n;i++)
    13 #define PER(i,n0,n) for(i=n;i>=n0;i--)
    14 #define MAX(a,b) ((a>b)?a:b)
    15 #define MIN(a,b) ((a<b)?a:b)
    16 #define CLR(a,x) memset(a,x,sizeof(a))
    17 #define rei register int
    18 using namespace std;
    19 typedef long long ll;
    20 const int maxn=100010;
    21 
    22 ll rd(){
    23     ll x=0;char c=getchar();int neg=1;
    24     while(c<'0'||c>'9'){if(c=='-') neg=-1;c=getchar();}
    25     while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
    26     return x*neg;
    27 }
    28 
    29 int N,M;
    30 ll tr[maxn],itr[maxn];
    31 
    32 inline void add(int x,ll y){
    33     ll yy=y*x;
    34     while(x<=N){
    35         tr[x]+=y;itr[x]+=yy;x+=lowb(x);
    36     }
    37 }
    38 inline ll query(int x){
    39     ll re=0,n=x;
    40     while(x){
    41         re+=(n+1)*tr[x]-itr[x];x-=lowb(x);
    42     }return re;
    43 }
    44 
    45 int main(){
    46     rei i,j,k;
    47     N=rd(),M=rd();ll lst=0;
    48     for(i=1;i<=N;i++){
    49         ll x=rd();add(i,x-lst);lst=x;
    50     }for(i=1;i<=M;i++){
    51         int a=rd(),b=rd(),c=rd();
    52         if(a==1){
    53             ll k=rd();add(b,k);add(c+1,-k);
    54         }else{
    55             printf("%lld
    ",query(c)-query(b-1));
    56         }
    57     }
    58     return 0;
    59 }
    View Code

    二维的话也是可以做的,就相当于每跳到一个x都跳一遍y的lowbit,而且可以支持区间修改和区间查询

    单点修改区间查询不多说,来说说区间修改和查询

    仿照一维的形式,我们先得到差分数组

    因为差分数组的前缀和就是原来那个点的值,所以能得到$d[i][j]=a[i][j]-a[i-1][j]-a[i][j-1]+a[i-1][j-1]$

    所以如果是(x1,y1,x2,y2)区间加的话,就是给(x1,y1),(x2+1,y2+1)加,给(x1,y2+1),(x2+1,y1)减(手画一下)

    然后是查询,也是有$(x1,y1,x2,y2)=S[x2][y2]-S[x1-1][y2]-S[x2][y1-1]+S[x1-1][y1-1]$

    然后有

    $$S[x][y]=sumlimits_{i=1}^{x}{sumlimits_{j=1}^{y}{sumlimits_{k=1}^{i}{sumlimits_{l=1}^{j}{d[k][l]}}}}$$

    $$=sumlimits_{i=1}^{x}{sumlimits_{k=1}^{i}{sumlimits_{j=1}^{y}{((y+1)d[k][j]-j*d[k][j])}}}$$

    $$=sumlimits_{i=1}^{x}{sumlimits_{j=1}^{y}{(x+1)(y+1)d[i][j]-(x+1)*j*d[i][j]-(y+1)*i*d[i][j]+i*j*d[i][j]}}$$

    于是二维树状数组维护d,id,jd和ijd就行了

    高级用法以后慢慢填

  • 相关阅读:
    计算机视觉概要和卷积神经网络基础概念
    详解类别不平衡问题
    机器学习中几种常见的模型评价指标
    目标检测中常用的IOU、NMS和mAP
    Trie树(字典树)的C++实现
    台大林轩田老师《机器学习技法》课程笔记2:Combining Predictive Features: Aggregation Models
    台大林轩田老师《机器学习技法》课程笔记1:Embedding Numerous Features: Kernel Models
    利用OpenCV在图片和摄像头中检测人脸
    台大林轩田老师《机器学习基石》课程笔记4:How Can Machines Learn Better?
    台大林轩田老师《机器学习基石》课程笔记3:How can machines learn?
  • 原文地址:https://www.cnblogs.com/Ressed/p/9533461.html
Copyright © 2011-2022 走看看