ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

树状数组学习笔记

2022-09-02 21:31:05  阅读:191  来源: 互联网

标签:10 树状 int lowbit 笔记 add 数组


 

一:树状数组定义

望文生义,树状数组就是用树形结构来模拟数组的一种数据结构。

二:图解(纯手绘,难看勿喷)

​编辑

C表示从1-k的和,

C[1]=a[1]

C[2]=C[1]+a[2]

C[3]=a[3]

C[4]=C[2]+C[3]+a[4]

C[5]=a[5]

C[6]=C[5]+a[6]

C[7]=a[7]

C[8]=C[4]+C[6]+C[7]+a[8]

C[9]=a[9]

C[10]=C[9]+a[10]

C[11]=a[11]

C[12]=C[10]+C[11]+a[12]

C[13]=a[13]

C[14]=C[13]+a[12]

C[15]=a[15]

C[16]=C[8]+C[12]+C[14]+C[15]+a[16]

三:可解决问题

单点修改,区间求和。

四:为何建立树状数组

我们可以发现树状数组中,一个数直接对应的数最多有logk+1个数,因此我们在求解一个区间和时,可以在O(log n) 内求解出。

五:与线段树的区别

树状数组能解决的问题线段树都能解决,线段树能解决的问题树状数组不一定能解决,但是!!!树状数组更快!!!树状数组更好调试!!!毕竟,能简单点谁不想简单呢。

六:如何查询树状数组子节点

根据上述建树过程我们可以发现,当前点所存的范围是(x-lowbit(x)+1,x)。

那么怎么实现lowbit()呢?

这需要用到二进制了。

计算机中有源码,反码和补码。

源码就是数据本来对应的二进制数;

正数的反码等于它本身。

负数的反码等于在其源码的基础上,符号位不变,其余位取反。

正数的补码等于它本身。

负数的补码等于其反码加一。

例如14=1110

反码:1110

-14的补码:1111

那么他的最低一位1可以油 14&-14。

我们也可以这样想,只要x不为0,那么x必有一位是1,取得他的负数后,最后那些是0的位数都会变为1,再加上1,则会将1都变为0,现在出现的最低一位1就是所求的最低的1,即lowbit()。

由于我们在树状数组中需要经常用到lowbit,因此我们可以将其写为一个函数,以便使用。

int lowbit(x)
{
    return x & -x
}

七:如何修改树状数组的值

对于上面的建树过程分析,我们可以发现当前这个数x会涵盖(x-lowbit(x)+1,x)内的元素,因此不难知道,对于每一个x的修改,都会影响到x+lowbit(x)这个数,直到影响到自己设定的最大值。

可以写出add函数

void add(int x,int k)
{
    for(int i=x;i<=maxx;i+=lowbit(x)) tr[i]+=k;
}

八:如何求和

继续分析建树过程,我们会发现x不会涵盖x-lowbit(x)的内容,所以我们求和时只需要加上

x-lowbit(x)所涵盖的元素即可。

void sum(int x)
{
    int sum=0;
    for(int i=x;i;i-=lowbit(i)) sum+=tr[i];
    return sum;
}

九:模板题目:洛谷:P3374 【模板】树状数组 1

 1 #include<iostream>
 2 
 3 using namespace std;
 4 const int N=5e5+10;
 5 int tr[N],n,m;
 6 
 7 int lowbit(int x)
 8 {
 9     return x & -x;
10 }
11 
12 void add(int x,int k)
13 {
14     for(;x<=n;x+=lowbit(x)) tr[x]+=k;
15 }
16 
17 int sum(int x)
18 {
19     int sum=0;
20     for(;x;x-=lowbit(x)) sum+=tr[x];
21     return sum;
22 }
23 
24 int main()
25 {
26     ios::sync_with_stdio(false);//树状数组题目中数据范围一般都比较大,最好用scanf或者cin加速读入
27     cin.tie(0);
28     
29     cin>>n>>m;
30     
31     for(int i=1;i<=n;i++) 
32     {
33         int x;
34         cin>>x;
35         add(i,x);//在第i个位置加上x
36     }
37     
38     while(m--)
39     {
40         int op;
41         cin>>op;
42         int x,y;
43         cin>>x>>y;
44         
45         if(op==1) add(x,y);
46         else cout<<sum(y)-sum(x-1)<<endl;//求出1到y和1到x-1的和,相减即为x-y的和
47     }
48 }

模板题,就不需要再做过多解释了吧。

模板题二:洛谷:【模板】树状数组 2

有些小伙伴可能看见这题,就会问了,树状数组不是只能做单点修改吗?

是的,但是我们可以用差分做这题啊!

想一想,1-n的差分加起来不就是n这个数么?有没有恍然大悟的感觉?

我们只需要在第x个位置上加上数据k,再在第y+1个位置上减去k不就能实现求任何一个位置的数了么?

 1 #include<iostream>
 2 
 3 using namespace std;
 4 const int N=5e5+10;
 5 int tr[N],n,m;
 6 
 7 int lowbit(int x)
 8 {
 9     return x & -x;
10 }
11 
12 void add(int x,int k)
13 {
14     for(;x<=n;x+=lowbit(x)) tr[x]+=k;
15 }
16 
17 int sum(int x)
18 {
19     int sum=0;
20     for(;x;x-=lowbit(x)) sum+=tr[x];
21     return sum;
22 }
23 
24 int main()
25 {
26     ios::sync_with_stdio(false);
27     cin.tie(0);
28     
29     cin>>n>>m;
30     
31     int last=0;
32     for(int i=1;i<=n;i++)
33     {
34         int x;
35         cin>>x;
36         add(i,x-last);
37         last=x;
38     }
39     
40     while(m--)
41     {
42         int op;
43         cin>>op;
44         
45         if(op==1)
46         {
47             int x,y,k;
48             cin>>x>>y>>k;
49             
50             add(x,k),add(y+1,-k);
51         }
52         
53         else
54         {
55             int x;
56             cin>>x;
57             cout<<sum(x)<<endl;
58         }
59     }
60 }

 十:拓展内容:

1:求逆序对:洛谷:P1908 逆序对

此题可以用树状数组边插入边求解。

何谓逆序对?就是i<j时,第i个数大于第j个数此类的。

我们可以用树状数组,在这个数的数值的位置加上一代表这个数被加入到了树状数组。(是数值,不是序号!!!)

那么怎么求有多少个逆序对呢?

我们可以用sum(x)求出在x之前有多少个数,可以边add()边求逆序对,因此第i个数逆序对的数量就等于i-sum(a[i]).

总体逆序对的数量只需要对其求和即可。

但是有一个问题,如果数据过大咋办,我们是不能开这么大的数组的。

这时候就需要用到离散化了。(可以选择的离散方式有很多,这里选择哈希离散)

 1 #include<iostream>
 2 #include<unordered_map>
 3 #include<algorithm>
 4 
 5 using namespace std;
 6 const int N=5e5+10;
 7 int tr[N],n,a[N];
 8 
 9 inline int lowbit(int x)
10 {
11     return x& -x;
12 }
13 
14 inline void add(int x,int k)
15 {
16     for(int i=x;i<=n;i+=lowbit(i)) tr[i]+=k;
17 }
18 
19 inline int sum(int x)
20 {
21     int sum=0;
22     for(int i=x;i;i-=lowbit(i)) sum+=tr[i];
23     return sum;
24 }
25 
26 unordered_map<int,int> s;
27 int l=0;
28 inline int get(int x)
29 {
30     if(!s.count(x)) s[x]=++l;
31     return s[x];
32 }
33 
34 int b[N];
35 inline void pai()
36 {
37     copy(begin(a),end(a),begin(b));
38     sort(b+1,b+1+n);
39     for(int i=1;i<=n;i++) get(b[i]);
40     
41 }
42 
43 
44 int main()
45 {
46     
47     scanf("%d",&n);
48     
49     long long ans=0;
50     
51     for(int i=1;i<=n;i++) scanf("%d",&a[i]);
52     
53     pai();
54     
55     for(int i=1;i<=n;i++)
56     {
57         int id=get(a[i]);
58         ans=ans+i-1-sum(id);
59         add(id,1);
60     }
61     
62     cout<<ans<<endl;
63     return 0;
64 }

注:由于笔者比较笨,又使用了一次copy函数,导致此题慢了很多。

2 .求树状数组中第k大数

1.二分法:

int find(int x)
{
    int l=0,r=maxx+1;
    while(l<r)
    {
        int mid=l+r>>1;
        if(sum(mid)<x) l=mid-1;
        else r=mid+1;
    }
    
    return r;
}

2.二进制法:

int find(int x)
{
    int ans=0,cnt=0;
    for(int i=20 ;i>=0;i--)//可以随意设置需要的大小,表示从二进制第i为开始
    {
        ans+=(1<<i);
        if(ans>=maxx||cnt+tr[ans]>=x) ans-=(1<<i);//目前的数大于最大值或者已经求出来的个数+当前值对应的数的个数>=自己要求的数则返回
        else cnt+=tr[ans];
        
    }
    return ans+1;//求出的是第x-1大的数,则ans+1为第x大的数。
}

 

(学习自acwing和网上大佬)

笔者CSDN:https://blog.csdn.net/qq_69908563/article/details/126669563

洛谷blog:https://www.luogu.com.cn/blog/buqiming/#

标签:10,树状,int,lowbit,笔记,add,数组
来源: https://www.cnblogs.com/buqiming/p/16651271.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有