zoukankan      html  css  js  c++  java
  • 树状数组

    首先,我们阐明一下为什么我们要学习树状数组,因为这个东西在某些方面来说要比线段树好一些,首先就是他自己比较好写,第二就是他在解决某些简单问题时,时间复杂度会比线段树更小一些。

    下面先来上一篇代码:
    大家可以认真看一下
    题目传送门(单点修改和区间查询)

    #include<iostream>
    #include<cstring>
    #define lowbit(x) x&-x
    using namespace std;
    int m,n;
    long long node[500005];
    void add(long long num,int x)
    {
        while(x<=n)
        {
            node[x]+=num;
            x+=lowbit(x);
        }
    }
    long long  query(int x)
    {
        long long ans;
        ans=0;
        while(x)
        {
            ans+=node[x];
            x-=lowbit(x);
        }
        return ans;
    }
    int main()
    {
        memset(node,0,sizeof(node));
        std::ios::sync_with_stdio(false);
        cin>>n>>m;
        long long a;
        for(int i=1;i<=n;++i)
        {
            cin>>a;
            add(a,i);
        }
        //int a;
        int c,d;
        long long e;
        for(int i=1;i<=m;++i)
        {
            cin>>a;
            if(a==1)
            {
                cin>>c>>e;
                add(e,c);
                //add(-e,d+1);
            }
            else
            {
                cin>>c>>d;
                cout<<(query(d)-query(c-1))<<endl;
            }
        }
        return 0;
    }
    

    那么下面我们就来简单的讲解一下树状数组,代码其实非常的简单,也非常的好理解,唯一不好理解的就是,lowbit和它建立的树的模样,在这里为大家提供一张图和一个实现lowbit操作的简单计算器,大家可以体会一下lowbit的神奇。

    代码

    #include<bits/stdc++.h>
    using namespace std;
    int main()
    {
    	int n;
    	cout<<"Welcome to use the tool of lowbit"<<endl;
    	for(;;)
    	{
    		cin>>n;
    		cout<<(n&(-n))<<endl;
    		cout<<"_________________________________"<<endl;
    	}
    	return 0;
    }
    

    图片

    这里写图片描述

    关键

    +lowbit跳到自己的爸爸,-lowbit跳到自己管辖范围的前面(这样就实现了求前缀和,当然你也可以存差分的东西这样就可以存当前位置节点的大小,后面会讲解)

    好了相信你在自己做了一些实验后已经感受到了lowbit的神奇,那么下面我们就来证明一下,博主很懒,这里粘一篇感觉写的很好的证明。

    首先明白一个概念,计算机中-i=(i的取反+1),也就是i的补码 
    而lowbit,就是求(树状数组中)一个数二进制的1的最低位,例如01100110,lowbit=00000010;再例如01100000,lowbit=00100000。 
    所以若一个数(先考虑四位)的二进制为abcd,那么其取反为(1-a)(1-b)(1-c)(1-d),那么其补码为(1-a)(1-b)(1-c)(2-d)。 
    如果d为1,什么事都没有-_-|||但我们知道如果d为0,天理不容2Σ( ° △ °|||)︴ 
    于是就要进位。如果c也为0,那么1-b又要加1,然后又有可能是1-a……直到碰见一个为补码为0的bit,我们假设这个bit的位置为x 
    这个时候可以发现:是不是x之前的bit的补码都与其自身不同?,x之后的补码与其自身一样都是0? 
    例如01101000,反码为10010111,补码为10011000,可以看到在原来数正数第五位前,补码的进位因第五位使其不会受到影响,于是0&1=0,; 
    但在这个原来数“1”后,所有零的补码都会因加1而进位,导致在这个“1”后所有数都变成0,再加上0&0=0,所以他们运算结果也都是零; 
    只有在这个数处,0+1=1,连锁反应停止,所以这个数就被确定啦O(∩_∩)O 
    所以and以后只有x这个bit是一……
    

    下面这里有一道升级版的题目,大家可以看一看。

    题目传送门(区间修改+单点求值)

    这里在上代码之前还是要做一番讲解,这个东西需要用到差分的思想,下面是一个关于差分思想的简介:

    来介绍一下差分
    
    设数组a[]={1,6,8,5,10},那么差分数组b[]={1,5,2,-3,5}
    
    也就是说b[i]=a[i]-a[i-1];(a[0]=0;),那么a[i]=b[1]+....+b[i];(这个很好证的)。
    
    假如区间[2,4]都加上2的话
    
    a数组变为a[]={1,8,10,7,10},b数组变为b={1,7,2,-3,3};
    
    发现了没有,b数组只有b[2]和b[5]变了,因为区间[2,4]是同时加上2的,所以在区间内b[i]-b[i-1]是不变的.
    
    所以对区间[x,y]进行修改,只用修改b[x]与b[y+1]:
    
    b[x]=b[x]+k;b[y+1]=b[y+1]-k;
    

    相信认真看了上面我写的“关键”你就可以知道这玩意儿怎么做了,下面上标程:

    #include<iostream>
    #include<cstring>
    #define lowbit(x) x&-x
    using namespace std;
    int m,n;
    long long node[500005];
    void add(long long num,int x)
    {
        while(x<=n)
        {
            node[x]+=num;
            x+=lowbit(x);
        }
    }
    long long  query(int x)
    {
        long long ans;
        ans=0;
        while(x)
        {
            ans+=node[x];
            x-=lowbit(x);
        }
        return ans;
    }
    int main()
    {
        memset(node,0,sizeof(node));
        std::ios::sync_with_stdio(false);
        cin>>n>>m;
        long long a,last;
        last=0;
        for(int i=1;i<=n;++i)
        {
            cin>>a;
            add(a-last,i);
            last=a;
        }
        //int a;
        int c,d;
        long long e;
        for(int i=1;i<=m;++i)
        {
            cin>>a;
            if(a==1)
            {
                cin>>c>>d>>e;
                add(e,c);
                add(-e,d+1);
            }
            else
            {
                cin>>c;
                cout<<query(c)<<endl;
            }
        }
        return 0;
    }
    

    后面还是会更新的······

  • 相关阅读:
    编译器与解释器的区别
    django中两张表之间有多个ForeignKey的问题
    23python装饰器的使用
    24.python传递可变长的非关键参数和关键参数
    如何让django的admin组件显示为中文
    22迭代器和iter()函数
    如何让django中的admin组件的表的字段显示为中文
    django模板中关于过滤器的使用
    django的认证支持模块
    python如何实现——日期加上天数的处理方法
  • 原文地址:https://www.cnblogs.com/mudrobot/p/13328852.html
Copyright © 2011-2022 走看看