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

    题目 树状数组模板1 题目大意:给定一个序列,要求支持两种操作:1.将某个数加上 (x),2.查询区间某部分的和。

    题目 树状数组模板2 题目大意:给定一个序列,要求支持两种操作:1.将区间内的每个数加上 (x),2.查询某个数。

    分析 由于第一题与第二题实际上是等价的(稍后会说明),我们以下仅讨论第一题。我们先以最简单的数组来考虑,显然操作1是 (O(1)),操作2是 (O(n)) 的,总体复杂度 (O(nm)),很明显会超时。这时我们就会想到用前缀和来优化操作2,这样的话操作2是 (O(1)) 的,但是操作1是 (O(n)) 的,总体复杂度仍为 (O(nm)),会超时。有没有一种数据结构,可以使操作1和操作2都是 (O(1)) 的呢?仅目前来看是不存在的。于是我们退而求其次寻找一种数据结构,使操作1和操作2都是 (O(log n)) 的。

    树状数组(又称二叉索引树,Binary Indexed Tree,BIT)是一类最基础的树形数据结构。它支持 (O(log n)) 进行区间修改单点查询或区间查询单点修改,亦即上述两道题的操作。具体来说,树状数组自身本质上仍是一个数组。我们令 (a) 为原数组,(c) 为树状数组,假定 (i) 二进制最后有 (j) 个0,则有 (c_i=sum_{k=0}^{2^j}a_{i-k})

    树状数组
    (图片来自网络,侵删)

    不难发现,现在我们更改与查询的时候只需更改包含着对象的位置就可以了,这样的位置不会超过 (log n) 个(为什么),所以我们只需 (O(1)) 从一个位置找到包含原对象的另一个位置就行了。

    如果当前要修改 (1(0001)),则要修改 (1(0001),2(0010),4(0100),8(1000))

    如果当前要修改 (5(0101)),则要修改 (5(0101),6(0110),8(1000))

    如果当前要查询 (7(0111)) 的前缀和,则要查询 (7(0111),6(0110),4(0100))

    如果当前要查询 (5(0101)) 的前缀和,则要查询 (5(0101),4(0100))

    不难发现,修改一个数时,下一个要修改的目标位置就是当前位置加上当前位置的最后一位,如 (6 ightarrow 8(0110+0010=1000));查询一个数时,下一个要修改的目标位置就是当前位置减去当前位置的最后一位,如 (5 ightarrow 4(0101-0001=0100))。那么现在问题转化为了如何 (O(1)) 求出某个数二进制最后一位,那便是lowbit函数:

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

    这个函数可以以 (O(1)) 的优秀复杂度求出某个数二进制的最后一位(比如 (3(0011) ightarrow 1(0001),6(0110) ightarrow 2(0010))),证明只需一点点基础的二进制编码,留给读者自证。

    那么现在我们就可以愉快地写出第一道题的代码。而对于第二题,不难发现,只要对原数列进行差分,就可以将区间修改转变为单点修改,单点查询转变为区间查询。

    代码
    第一题:

    #include<bits/stdc++.h>
    using namespace std;
    
    const int maxn = 5E+5 + 5;
    
    int n, m;
    int c[maxn];
    
    int Read()
    {
        int x = 0, op = 1;
        char ch = getchar();
        while(ch < '0' || ch > '9') {
            if(ch == '-') op = -1;
            ch = getchar();
        }
        while(ch >= '0' && ch <= '9') {
            x = (x << 3) + (x << 1) + (ch - '0');
            ch = getchar();
        }
        return x * op;
    }
    
    inline int lowbit(int x) { return x & (-x); }
    inline void Add(int pos, int x) { while(pos <= n) c[pos] += x, pos += lowbit(pos); }
    
    int Query(int pos)
    {
        int res = 0;
        while(pos) {
            res += c[pos];
            pos -= lowbit(pos);
        }
        return res;
    }
    
    int main()
    {
        scanf("%d%d", &n, &m);
        for(int i = 1; i <= n; ++i) Add(i, Read());
    
        while(m--) {
            int op = Read(), x = Read(), y = Read();
    
            if(op == 1) Add(x,y);
            else printf("%d
    ", Query(y) - Query(x - 1));
        }
    }
    

    第二题:

    #include<bits/stdc++.h>
    using namespace std;
    
    const int maxn = 5E+5 + 5;
    
    int n, m;
    int a[maxn], c[maxn];
    
    int Read()
    {
        int x = 0, op = 1;
        char ch = getchar();
        while(ch < '0' || ch > '9') {
            if(ch == '-') op = -1;
            ch = getchar();
        }
        while(ch >= '0' && ch <= '9') {
            x = (x << 3) + (x << 1) + (ch - '0');
            ch = getchar();
        }
        return x * op;
    }
    
    inline int lowbit(int x) { return x & (-x); }
    inline void Add(int pos, int x) { while(pos <= n) c[pos] += x, pos += lowbit(pos); }
    
    int Query(int pos)
    {
        int res = 0;
        while(pos) {
            res += c[pos];
            pos -= lowbit(pos);
        }
        return res;
    }
    
    int main()
    {
        scanf("%d%d", &n, &m);
        for(int i = 1; i <= n; ++i) a[i] = Read();
    
        while(m--) {
            int op = Read(), x = Read(), y, z;
    
            if(op == 1) y = Read(), z = Read(), Add(x, z), Add(y + 1, -z);
            else printf("%d
    ", a[x] + Query(x));
        }
    }
    
  • 相关阅读:
    Android SDK Android NDK 官方下载地址
    编码转换工具 源码
    st_mode的剖析
    关于 python 字符编码的一些认识
    MFC中的argc和argv参数
    VC实现文件拖拽获取文件名
    CString 转 int
    《C语言程序设计实践教程》实验题源程序
    C语言 文件操作 结构体与文件 fgetc fputc fread fwrite
    C++语言 创建状态栏
  • 原文地址:https://www.cnblogs.com/whx1003/p/11828666.html
Copyright © 2011-2022 走看看