树状数组入门与基础应用

树状数组入门与基础应用


介绍

树状数组或者二叉索引树也称作BIT;它的查询和修改的时间复杂度都是O(log(n)),空间复杂度则为O(n),这是因为树状数组通过普通数组线性结构转化成树状结构,从而进行跳跃式扫描。通常使用在高效的计算数组的前缀和,区间和。

先通过这个例子引出树状数组的概念。
对长度为n的数列{a1,a2,a3,…an},进行如下操作。
(1)修改元素函数add( x , d ):把ax加上d
(2)求和函数sum( x ):即求前x项数的和,区间和[ l , r ]便为sum( r ) - sum( l - 1 )。

#define lowbit(x) ((x) & -(x))
void add(int x, int d)//修改树中和ax有关的tree[]
{
    while (x <= n)
    {
        tree[x] += d;
        x += lowbit(x);
    }
}
int sum(int x)//求和
{
    int sum = 0;
    while (x)
    {
        sum += tree[x];
        x -= lowbit(x);
    }
    return sum;
}


c[1] = a[1];//1
c[2] = a[1] + a[2];//10
c[3] = a[3];//11
c[4] = a[1] + a[2] + a[3] + a[4];//100
c[5] = a[5];//101
c[6] = a[5] + a[6];//110
c[7] = a[7];//111
c[8] = a[1] + a[2] + a[3] + a[4] + a[5] + a[6] + a[7] + a[8];//1000
可以发现,这颗树是有规律的.
c[i] = a[i - 2k+1] + a[i - 2k+2] + … + a[i]; //k为i的二进制中从最低位到高位连续0的长度。
而2^k = i&(-i) 即为lowbit(x)所求。


模板与基础应用

洛谷 P3374 【模板】树状数组 1

如题,已知一个数列,你需要进行下面两种操作:
1.将某一个数加上 xx
2.求出某区间每一个数的和

#include <bits/stdc++.h>
using namespace std;
#define lowbit(x) ((x) & -(x))
const int N = 500010;
int tree[N];
int n, m;
void add(int x, int d)
{
    while (x <= n)
    {
        tree[x] += d;
        x += lowbit(x);
    }
}
int sum(int x)
{
    int sum = 0;
    while (x)
    {
        sum += tree[x];
        x -= lowbit(x);
    }
    return sum;
}
int main()
{
    ios::sync_with_stdio(0);
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
    {
        int x;
        cin >> x;
        add(i, x);
    }
    for (int i = 1; i <= m; i++)
    {
        int t, x, y;
        cin >> t >> x >> y;
        if (t == 1)
            add(x, y);
        else
            cout << sum(y) - sum(x - 1) << endl;
    }
    return 0;
}

洛谷 P3368 【模板】树状数组 2

如题,已知一个数列,你需要进行下面两种操作:
1.将某区间每一个数数加上 xx;
2.求出某一个数的值。

#include <bits/stdc++.h>
using namespace std;
#define lowbit(x) ((x) & -(x))
const int N = 500005;
int tree[N], b[N];
int n, m, tem;
void add(int x, int d)
{
    while (x <= n)
    {
        tree[x] += d;
        x += lowbit(x);
    }
}
int sum(int x)
{
    int sum = 0;
    while (x)
    {
        sum += tree[x];
        x -= lowbit(x);
    }
    return sum;
}
int main()
{
    ios::sync_with_stdio(0);
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
    {
        int x;
        cin >> x;
        if (i == 1)
            b[i] = x;
        else
            b[i] = x - tem;
        tem = x;
        add(i, b[i]);
    }//这里建立差分数组
    for (int i = 1; i <= m; i++)
    {
        int t, x, y, d;
        cin >> t;
        if (t == 1)
        {
            cin >> x >> y >> d;
            add(x, d);
            add(y + 1, -d);
        }//由于区间修改差分数组只改变x,y+1节点的数值
        else
        {
            cin >> x;
            cout << sum(x) << endl;
        }//差分数组前x项和便为第x项数值
    }
    return 0;
}

另树状数组也常用离散化的方法求逆序对的个数

洛谷 P1966 火柴排队

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
const int maxn = 99999997;
#define lowbit(x) ((x) & -(x))
struct node
{
    int num;
    int pos;
} a[N], b[N];
int c[N], n, tree[N];
void add(int x, int d)
{
    while (x <= n)
    {
        tree[x] += d;
        tree[x] %= maxn;
        x += lowbit(x);
    }
}
int sum(int x)
{
    int sum = 0;
    while (x)
    {
        sum += tree[x];
        sum %= maxn;
        x -= lowbit(x);
    }
    return sum;
}
bool cmp(node a,node b)
{
    return a.num < b.num;
}
int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n;i++)
    {
        scanf("%d", &a[i].num);
        a[i].pos = i;
    }
    for (int i = 1; i <= n;i++)
    {
        scanf("%d", &b[i].num);
        b[i].pos = i;
    }
    sort(a + 1, a + 1 + n, cmp);
    sort(b + 1, b + 1 + n, cmp);//此离散化手法为用数排数列的第几大代表数
    for (int i = 1; i <= n;i++)
        c[a[i].pos] = b[i].pos;//离散化 c[i]=i为此题要达到的目标即求逆序对
    int ans = 0;
    for (int i = 1; i <= n;i++)
    {
        add(c[i], 1);
        ans += i - sum(c[i]);//由于树节点的加入有先后,所以sum(x)是节点左侧比x小的数的多少
        ans %= maxn;
    }
    printf("%d\n", ans);
    return 0;
}

本文章部分数据说明来自树状数组详解

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值