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

    <前言>

    本次依旧是高手训练专题解析。

    但与以往不同的是,这次会附上树状数组基础内容。

    本篇为基础内容。


    <树状数组>

    在此贴出大哥的blog,有更加全面、系统的介绍。

    什么是树状数组?怎么用树状数组?树状数组有什么应用?

    什么是树状数组?

    树状数组 是一种数据结构, 可以在(O(log_2 n ))时间内完成 **修改、查询 **等序列操作。

    它可以处理区间、单点加&查询,以及前(后)缀最值

    它利用了(lowbit())运算的一些性质。

    观察(1...10)(mathrm{lowbit})序列:

    序号 (lowbit) 二进制
    1 1 1
    2 2 10
    3 1 11
    4 4 100
    5 1 101
    6 2 110
    7 1 111
    8 8 1000
    9 1 1001
    10 2 1010

    容易发现(lowbit)就是在求最低位的1所代表的数。

    而我们可以通过补码的性质快速求(lowbit)

    #define lowbit(x) (x & (-x))
    

    那么我们可以根据这个性质搞出一棵树(森林),使一些点对应一段区间。

    大概长这样:((mathrm{x})连向(mathrm{y})的边代表(mathrm{y + lowbit(y) = x}))

    那么我们通过改变一个值的大小来代表改变一段区间的(子树)所有数的大小。

    比如点(x)对应(可以理解为一种代表)([x - mathrm{lowbit(x)} + 1, x])一段区间内的数。对这一段区间进行区间加的时候只需要修改x就行了。

    那么对于序列({a_i}) ,我们需要维护一个({c_i}),定义如下

    [c_n = sum_{i = n - mathrm{lowbit(n) + 1}}^{n} a_i ]

    (c_n)为所有子树内(a_i)的和。

    如何使用树状数组

    我们需要掌握修改、查询等操作

    修改

    我们进行修改操作的时候,可以选择是进行前缀修改还是后缀修改。

    本质上都是利用了树状数组的性质,所以按照习惯来吧。

    修改操作之前说过只需要修改某些特定的点就行了。

    比如修改([1, 9]) 一段区间。

    我们可以先([1, N])修改,再([9 + 1, N])逆修改。

    修改之后的树形态:

    我们只修改了少数点,而且个数是(mathrm{O(log_2 n)})级别的,数量越大优势越明显。

    (mathrm{Code:})

    inline void inc(int x, int v) {for(; x <= MAX
    N; x += lowbit(x)) c[x] += v;}
    

    查询

    那么有人就要问了,这个修改后的形态这么鬼畜,改怎么获得正确的查询信息呢?

    我们发现(lowbit)的另一个快乐的性质:

    [y = y - lowbit(y)操作能使y到达上一棵同级(或上级)子树的树根 ]

    比如上面那个栗子,无论9还是10都可以通过这个运算到达8.

    那不是很爽么。单次修改中某个点若在范围内,则其同级子树必有一个被修改。直接通过(lowbit)累计即可。

    容易发现

    [a_n = sum_{i = n; i; i -=lowbit(i)}c_i ]

    我们可以在(O(log_2n))时间内完成查询。

    (mathrm{Code:})

    inline int ask(int x) {
        int sum = 0;
        for (; x; x -= lowbit(x)) sum += c[x];
        return sum;
    }
    

    一些拓展

    比如前缀(后缀)最值,可以通过树状数组快速维护。

    代码实现十分简单,只需要将("+") ("-")改成("max") ("min")

    Just like:

    inline void inc(int x, int v) {for (; x <= MAXN; x += lowbit(x)) c[x] = max(c[x], v);} 
    //操作这里min也是一样的
    inline int ask(int x) {
        int maxn = -1;
        for (; x; x -= lowbit(x)) maxn = max(maxn, c[x]); //注释同上
        return maxn;
    }
    

    二维操作&各种基本功能实现详见Pαrsnip的blog

    还有比如什么树状数组上二分(倍增)、一些小Trick、简单代替线段树等黑科技。

    尽在cz的树状数组黑科技讲义.

    树状数组应用

    一维/二维/(你想写高维可能也没什么)区间/单点修改,区间/单点查询的 区间四则运算/前缀(后缀)最值

    二维偏序,逆序对等,事实上很多偏序问题树状数组是首选。

    当然更多的应用无处不在与各个地方。

    题目基本是让你求一个序列中所有连续子序列的某个贡献值。


    <后记>

    为树状数组专题备用基础讲解的blog。

  • 相关阅读:
    POJ 1182 食物链 +POJ 2492 A Bug's Life 种类并查集
    Codeforces 884C Bertown Subway dfs判环
    HDU 2102 A计划 bfs
    Codeforces Round #442 (Div. 2) 877E
    Codeforces Round #443 (Div. 2) B. Table Tennis
    Codeforces Round #442 (Div. 2) D. Olya and Energy Drinks
    HDU 1495 非常可乐 BFS/数论
    Codeforces Round #442 (Div. 2) B. Nikita and string DP
    51Nod 1102 面积最大的矩形 +1272 最大距离 单调栈
    HDU 1286 找新朋友
  • 原文地址:https://www.cnblogs.com/yywxdgy/p/13098913.html
Copyright © 2011-2022 走看看