树状数组
树状数组专题
[update]网上蒯了个区间加区间求和的板子,姑且先放着吧
1 inline void add(int p, LL x) { 2 for(int i = p; i <= n; i += i & -i) { 3 sum1[i] += x; 4 sum2[i] += x * p; 5 } 6 } 7 inline void range_add(int l, int r, LL x) { 8 add(l, x); 9 add(r + 1, -x); 10 } 11 inline LL ask(int p) { 12 LL ans = 0; 13 for(int i = p; i; i -= i & -i) { 14 ans += (p + 1) * sum1[i] - sum2[i]; 15 } 16 return ans; 17 } 18 inline LL range_ask(int l, int r) { 19 return ask(r) - ask(l - 1); 20 }
[update]树状数组第i位存的是[i - lowbit(i) + 1, i]的和。
树状数组上二分找第k个:
inline int getKth(int k) { int ans = 0, t = pw[n]; while(t >= 0) { if(((ans | (1 << t)) <= n) && ta[ans | (1 << t)] < k) { k -= ta[ans | (1 << t)]; ans |= (1 << t); } t--; } return ans + 1; }
前言:
树状数组就算不理解也要强行背下来(很好背),以后做题就会发现根本离不开它。
首先,什么是树状数组?
①是一种数据结构
②支持单点修改,区间求和。魔改后会支持区间加,单点求和,以及进化版的二维树状数组等等,在此按下不表。
③插入O(log2n),查询O(log2n)
原理是关于二进制的。我没怎么理解。
那么先放最基础的模板。
1 #define lowbit(x) (x&(-x))
2 int tree[100010],n;
3 void add(int x,int v)///给x加上v,初始化
4 {
5 for(int i=x;i<=n;i+=lowbit(i)) tree[i]+=v;
6 return;
7 }
8 int getsum(int x)///查询x处的前缀和
9 {
10 int ans=0;
11 for(int i=x;i>0;i-=lowbit(i)) ans+=tree[i];
12 return ans;
13 }
14 int ask(int l,int r)///查询区间和
15 {
16 if(l>r) return 0;
17 return getsum(r)-getsum(l-1);
18 }
十分之基础
接下来就是一些例题与拓展应用。
P3368 模板树状数组2
这道题要区间加,单点差。考虑到这是个树状数组模板题(???????),我们就考虑差分。
差分:b[i]=a[i]-a[i-1];
对于[l,r]+v,只需b[l]+v,b[r+1]-v即可
a[i]=∑(b[1]...b[i]);
那么我们就要对b[]实施单点改,区间求和。使用树状数组:
1 #include <cstdio> 2 #define lowbit(a) (a&(-a)) 3 using namespace std; 4 typedef long long LL; 5 const int N = 500010; 6 LL a[N],tree[N],n; 7 void add(int x,int v) 8 { 9 for(int i=x;i<=n;i+=lowbit(i)) tree[i]+=v; 10 return; 11 } 12 LL getsum(int x) 13 { 14 LL ans=0; 15 for(int i=x;i;i-=lowbit(i)) ans+=tree[i]; 16 return ans; 17 } 18 LL ask(int l,int r) 19 { 20 if(l>r) return 0; 21 return getsum(r)-getsum(l-1); 22 } 23 int main() 24 { 25 int m,x,y,k,flag; 26 scanf("%d%d",&n,&m); 27 for(int i=1;i<=n;i++) 28 { 29 scanf("%d",&a[i]); 30 add(i,a[i]-a[i-1]); 31 } 32 while(m--) 33 { 34 scanf("%d",&flag); 35 if(flag==1) 36 { 37 scanf("%d%d%d",&x,&y,&k); 38 add(x,k); 39 add(y+1,-k); 40 } 41 else 42 { 43 scanf("%d",&x); 44 printf("%lld\n",ask(1,x)); 45 } 46 } 47 return 0; 48 }
(当然也可以用线段树水过)
1 #include <cstdio> 2 using namespace std; 3 const int N = 500010; 4 typedef long long LL; 5 LL sum[4*N],tag[4*N]; 6 void down(int l,int r,int o) 7 { 8 if(tag[o]) 9 { 10 tag[o<<1]+=tag[o]; 11 tag[o<<1|1]+=tag[o]; 12 int mid=(l+r)>>1; 13 sum[o<<1]+=tag[o]*(mid-l+1); 14 sum[o<<1|1]+=tag[o]*(r-mid); 15 tag[o]=0; 16 } 17 return; 18 } 19 void update(int l,int r,int o) 20 { 21 sum[o]=sum[o<<1]+sum[o<<1|1]; 22 return; 23 } 24 void build(int l,int r,int o) 25 { 26 if(l==r) 27 { 28 scanf("%lld",&sum[o]); 29 return; 30 } 31 int mid=(l+r)>>1; 32 build(l,mid,o<<1); 33 build(mid+1,r,o<<1|1); 34 update(l,r,o); 35 return; 36 } 37 void add(int L,int R,int v,int l,int r,int o) 38 { 39 if(L<=l&&r<=R) 40 { 41 sum[o]+=v; 42 tag[o]+=v;///this!一开始我忘写了 43 return; 44 } 45 int mid=(l+r)>>1; 46 down(l,r,o); 47 if(L<=mid) add(L,R,v,l,mid,o<<1); 48 if(R>mid) add(L,R,v,mid+1,r,o<<1|1); 49 update(l,r,o); 50 return; 51 } 52 LL ask(int L,int R,int l,int r,int o) 53 { 54 if(L<=l&&r<=R) return sum[o]; 55 if(r<L||R<l) return 0; 56 down(l,r,o); 57 int mid=(l+r)>>1; 58 return ask(L,R,l,mid,o<<1)+ask(L,R,mid+1,r,o<<1|1); 59 } 60 int main() 61 { 62 int m,n,flag,x,y,k; 63 scanf("%d%d",&n,&m); 64 build(1,n,1); 65 while(m--) 66 { 67 scanf("%d",&flag); 68 if(flag==1) 69 { 70 scanf("%d%d%d",&x,&y,&k); 71 add(x,y,k,1,n,1); 72 //for(int i=1;i<=n;i++) printf("%d ",ask(i,i,1,n,1)); 73 //printf("\n"); 74 } 75 else 76 { 77 scanf("%d",&x); 78 printf("%lld\n",ask(x,x,1,n,1)); 79 } 80 } 81 return 0; 82 }