我会树状数组啦哈哈哈

  树状数组资瓷的基本操作有两个,第一个是查询前缀和,第二个是单点增加。利用差分的思想可以达到区间增加和单点查询的操作,复杂度全是log元素数量,十分优秀。

  (说起来用c[i]减c[i-1]也可以在2*logn的时间单点查询.

  我甚至还能存一下本来的数组,就是O(1)了)

 

  如果不用增加了的话也可以建立数组sum[i]=sum[i-1]+o[i],查询时间也是1.

  这里的c数组就是树状数组的表示方式了。这个玩意儿真的是靠lowbit()一手撑起来的数据结构了……

  lowbit(i)表示i二进制分解后第一个出现1的位置,也是c[i]在原数组中表示的元素和的长度。

  lowbit()的代码如下。什么?你问我证明?它就是那样的嘛,我的老师又没讲,怪我喽。

int lowbit(int x)//这里的x一定要是非负整数int
{
     return x&(-x);
}

 

  怎么利用呢,就是c[i]=∑o[f],(i-lowbit(i)<f<=i);为什么呢?因为建立的时候就是按这个规则建立的,我这样说你懂了吧?

  这个时候应该放一个图了……

  

  其实只要看这个图自己手推一会就能明白很多了,推荐大家输出一下1-9的lowbit(),一点就通。

  假设我们已经建好了,那输出sum[n]就可以把c[n]利用lowbit()向前推,一直推进零里,经过的节点一定完全覆盖了1-n的原数组了

1 for(i=n,ans=0;i!=0;i-=lowbit(i))
2     ans+=c[i];

  证明嘛,自己看图好了。

  那么如何建立呢?根据图可以得出c[i]的父节点是c[i+lowbit(i)],那么c[i]加一个值它的父节点们也加一波就完事了。

  它的空间复杂度是n,也就是说加到c[n]以后一定停下来,那么就有代码

for(f=i;f<=n;f+=lowbit(f))//n为原数组元素数量
    c[f]+=o[i];

  修改的话是差不多的。比如把o[x]加t就是这样弄

o[x]+=t;
for(;x<=n;x+=lowbit(x))
    c[x]+=t;

  t当然也能是负数啦。

                                                                                         

  以上是基础操作。那么如何利用它的特性进行区间增加和单点查询呢?

  有一个很奇妙且不好想的方法就是利用差分。

  我们令原数组为o[i],处理出差分数组a[i]=o[i]-o[i-1]。此时o[1]=a[1],o[2]=a[1]+a[2],o[3]=a[1]+a[2]+a[3],……以此类推。然后再对于a[i]处理出c[i],就可以利用树状数组在logn的事件查询出∑a[i](1<=i<=n)的值,它就是原来的原来数组o[i]了。

  区间增加相当于把该区间内的数同时加t,那么区间内相邻元素的变化量是不变的,而o[左端点]就要比o[左端点-1]在原来基础上多t,o[右端点+1]比o[右端点]在原来基础上少了t。

  因此要把a[左端点]+=t,a[右端点+1]-=t。对c的操作就照搬上面了。

 

 

  来一个例题好了。

  

 

   想用动态规矩的朋友们就算了,32000*32000搞不了的,前缀和也没啥用。

  那么这题咋弄呢?他需要一个n*log?级别的算法,可以想一想树状数组了,支持修改和查询刚好是前缀和。

  既然输入的时候已经排好序了,我们每次输入进来一个点就输出xi和xi以前点的个数,这个需要logxi的时间,然后再把xi这一点的个数++(也就是第一种树状数组的单点增加的操作)这样跑一边就是n*log32000的复杂度,十分优秀。

  然而x可以等于0,在add的操作中由于lowbit(0)=0.就动不了了,导致超时并且丢50分。我的一个方法是设置一个sum0专门表示x=0的点的个数:输入时总输出sum(x)+sum0,如果这个点x=0就sum0++,否则add(x);

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstdlib>
 4 #include<cstring>
 5 #include<cmath>
 6 #include<string>
 7 #include<algorithm>
 8 #include<vector>
 9 #include<map>
10 #include<stack>
11 #include<queue>
12 #include<deque>
13 #include<set>
14 using namespace std;
15 int i,tx,ty;
16 int n;
17 int c[32010],sum0;
18 int lowbit(int x)
19 {
20     return x&(-x);
21 }
22 void add(int p)
23 {
24     
25     while(p<=32000)
26     {
27         c[p]++;
28         p+=lowbit(p);
29     }
30 }
31 int sum(int x)
32 {
33     int ans=0;
34     while(x)
35     {
36         ans+=c[x];
37         x-=lowbit(x);
38     }
39     return ans;
40 }
41 int main()
42 {
43 ios::sync_with_stdio(false);
44 cin.tie(NULL);
45 cout.tie(NULL);
46 //freopen("123.in","r",stdin);
47     cin>>n;
48     for(i=1;i<=n;i++)
49     {
50         cin>>tx>>ty;
51             cout<<sum(tx)+sum0<<endl;
52         if(tx==0)sum0++;
53         else
54             add(tx);
55         
56     }
57 }
(*^▽^*)

 


 

  以上还是基础操作。我们如何用树状数组完成区间修改区间查询的操作呢。这显然是线段树的活,现在考虑如何用树状数组维护。

  以下需要乘法分配律的知识。

  既然区间求和还是想差分。先对于原数组o[i]求一个差分数组b[i]=a[i]-a[i-1];那么查询区间和还可以使用容斥减一减,看来只需要求[1,x]的区间就行了。

  我们推一下这两个式子:这里a是原数组,d是差分数组。

(转载自https://www.cnblogs.com/lcf-2000/p/5866170.html)

 

 

 

   用两个树状数组维护d[i]与d[i]*i即可。

  哎呀真丢人。

  放一个模板算了。

 1 int i,t,r,l,v;
 2 int n,m;
 3 int a[100010],b[100010];
 4 int c1[100010],c2[100010];
 5 inline int lowbit(int x)
 6 {
 7     return x&(-x);
 8 }
 9 inline void add1(int x,int v)
10 {
11     for(;x<=n;x+=lowbit(x))
12     {
13         c1[x]+=v;
14     }
15     return ;
16 }
17 inline void add2(int x,int v)
18 {
19     for(;x<=n;x+=lowbit(x))
20     {
21         c2[x]+=v;
22     }
23     return ;
24 }
25 inline int sum1(int x)
26 {
27     int ans=0;
28     for(;x;x-=lowbit(x))
29     {
30         ans+=c1[x];
31     }
32     return ans;
33 }
34 inline int sum2(int x)
35 {
36     int ans=0;
37     for(;x;x-=lowbit(x))
38     {
39         ans+=c2[x];
40     }
41     return ans;
42 }
43 int main()
44 {
45     n=read();m=read();/*
46     for(i=1;i<=n;i++)
47     {
48         a[i]=read();t=a[i]-a[i-1];
49         add1(i,t);
50         add2(i,(i-1)*t);
51     }*/
52     for(i=1;i<=m;i++)
53     {
54         t=read();
55         if(t==1)//修改操作
56         {
57             l=read();r=read();v=read();
58             add1(l,v);
59             add1(r+1,-v);
60             add2(l,v*(l-1));
61             add2(r+1,-v*r);
62         }
63         else//求和操作
64         {
65             l=read();r=read();
66             write( r*sum1(r)-(l-1)*sum1(l-1)-sum2(r)+sum2(l-1));
67             putchar(10);
68         }
69     }
70     return 0;
71 } 
-.-

虽然代码也长,但是比线段树看起来好懂许多吧。跑得还快。

posted @ 2018-07-17 21:29  zzuqy  阅读(214)  评论(1编辑  收藏  举报