• 树状数组


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

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

    #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;
    }
    

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

  • 相关阅读:
    Java 蓝桥杯 算法训练 貌似化学
    Java 蓝桥杯 算法训练 貌似化学
    Java 蓝桥杯 算法训练 字符串的展开 (JAVA语言实现)
    Java 蓝桥杯 算法训练 字符串的展开 (JAVA语言实现)
    Java 蓝桥杯 算法训练 字符串的展开 (JAVA语言实现)
    Java 蓝桥杯 算法训练 字符串的展开 (JAVA语言实现)
    Java 蓝桥杯 算法训练 字符串的展开 (JAVA语言实现)
    JAVA-蓝桥杯-算法训练-字符串变换
    Ceph:一个开源的 Linux PB 级分布式文件系统
    shell 脚本监控程序是否正在执行, 如果没有执行, 则自动启动该进程
  • 原文地址:https://www.cnblogs.com/mudrobot/p/13328852.html
Copyright © 2020-2023  润新知