• 【模板篇】树状数组们(一)


    以下文章逻辑混乱,请确保精神正常后再观看。

    树状数组???
    百度上讲的非常的多,各方面的资料也都有所涉及,大家不懂的可以去逛一圈搜一搜。
    但树状数组能干的事情非常多,我看也没有很详细的总结,就来浪一波,斗胆讲一讲树状数组。。各位看官,要本蒟蒻讲的不好,你们轻喷。。
    树状数组,作为一个nlogn数据结构,有着得天独厚 的优势。比如,常数小,码短好写。。
    当然也是有一些缺点,比如很多事干不了。。然而在他能干的领域里面,效率能甩别的数据结构几条街。

    为什么要搞树状数组???
    快。
    就拿树状数组最基础的例子来讲,让你维护一个数列,支持如下操作:
    1)修改某个点i的值
    2)查询区间[L,R]的数的总和
    如果用数组来搞的话,会TLE的飞起。。。
    这时候就需要一些nlogn的数据结构来救世了,比如:树状数组。
    当然会有神犇@一些像线段树一类的数据结构,但树状数组比其他的要好写不少,常数也会小不少。。
    然后树状数组最基本的原理是什么嘛,我真的讲不清楚(其实我不会),所以你们可以自己百度。
    我只能说说大体意思。。
    其实有张图会更明白是么。。(图片来自百度百科)
    树状数组示意图
    然后树状数组之流中有其特有的名片——lowbit~~
    有了lowbit就知道是树状数组了。。。

    lowbit是什么呢???
    lowbit(i)指的是i在二进制表示下最低位的1及其后的所有0组成的值
    比如36:在二进制下是100 1 00,最低位的1我加粗了,代表了4,所以lowbit(36)就是4。。
    从图上可以看出,对于树状数组上的节点,第i个节点控制了(i-lowbit(i),i]的区间。。
    比如节点1控制(0,1],节点4控制(0,4],(0是凑的),节点6控制(4,6](其实就是[5,6]啦)……
    所以这么一联系就看出树状数组和lowbit密切相关。。

    那如何求lowbit???
    一位一位比嘛,不在乎那点效率,当然有快速的方法啦~
    由百度百科(我说了你们听不懂):
    设节点编号为x,那么这个节点管辖的区间为2^k(其中k为x二进制末尾0的个数)个元素。因为这个区间最后一个元素必然为Ax,
    所以很明显:Cn = A(n – 2^k + 1) + … + An
    这样算2^k易得(你要看不懂就不要管为什么了) lowbit(x)=x&(x^(x-1))①
    然后计算机有个非常好的性质叫补码(就是一个二进制数相反数的表示),就是取反再加一。
    所以①可简化为:lowbit(x)=x&-x !!!!真的好简便呢。。。

    好吧,那么树状数组是怎么维护上面提到的两个问题的呢???
    好吧,再去看图。。看图是有用的。。看到上面无比优美的性质,我们可以用C数组维护前缀和呢。。
    没错,Ci可以保存1~i的前缀和,怎么求呢?
    还记得大明湖畔的lowbit么?
    (怎么不记得,他费了我半天劲才让我弄懂呢)
    我们让一个点不断的减lowbit,然后把遇到的点的值都加上,就是前缀和了!!不信你可以找几个点蹦一下试试 ,没错就是这么神奇╮(╯_╰)╭……
    所以求[L,R]的和,就用[1,R]的和减去[1,L-1]的和就行了~~
    //什么玩意,我用数组预处理也能做到嘛好像还O(n)呢,你这O(nlogn)算啥嘛。。。
    别急,还有修改呢。。你预处理就没法修改了吧。。。

    所以如何修改呢???
    单点修改嘛。。看图。。从i点开始不断的加lowbit,直到超出范围为止,单点的维护就搞定了~~ 你可以再找几个点蹦蹦试试

    具体的原理基本就这样,我自己都觉得讲得不清楚。。
    所以还是上代码吧,说不定你们能从代码中洞悉这般奥妙。。

    #include <cstdio>
    
    #define gc getchar
    
    class Binary_Tree1{ //树状数组1
    public:
        inline int getnum() {
            int a = 0; char c = gc(); bool f = 0;
            for (; (c<'0' || c>'9') && c != '-'; c = gc());
            if (c == '-') f = 1, c = gc();
            for (; c >= '0'&&c <= '9'; c = gc()) a = (a << 1) + (a << 3) + c - '0';
        } //读入优化
    
        inline void putnum(int x) { //快速输出
            int c[15], cnt = 0;
            for (; x; x /= 10)
                c[++cnt] = x % 10;
            for (; cnt; cnt--)
                putchar(c[cnt] + '0');
        }
    
        Binary_Tree1(int n) :n(n) {
    
        } //没任何卵用的构造函数╮(╯_╰)╭
    
        Binary_Tree1() {
    
        }
    
        void Binary_Tree1_Init() {
            for (int i = 1; i <= n; i++)
                add(i, getnum());
        } //读入的初始化
    
        void add(int x, int i) {      //单点x加i
            for (; x <= n; x += lb(x))
                c[x] += i;
        }
    
        int sum(int x) {          //查前x项的和
            int s = 0;
            for (; x; x -= lb(x))
                s += c[x];
            return s;
        }
    
        int query(int L, int R){     //区间查[L,R]的和
            return sum(R) - sum(L - 1);
        }
    private:
        static const int MAXN = 500005;
    
        inline int lb(int x) { //lb就是lowbit,我嫌字母多就缩写了
            return x&(-x);
        }
    
        int c[MAXN], n;
    };
  • 相关阅读:
    glibc源码下载
    指令查找工具
    ubuntu下ldd,查看程序动态库信息
    gdb使用记录
    找到返回地址(1)
    vim自动格式化
    android 注入so
    Mac下安装m2crypto 解决找不到openssl头文件的错误
    Mac下android studio卡,居然这么解决了。。。。
    git忽略文件
  • 原文地址:https://www.cnblogs.com/enzymii/p/8412159.html
Copyright © 2020-2023  润新知