P3374 【模板】树状数组 1


原题链接

P3374
题目类型: 普 及 / 提 高 − {\color{yellow} 普及/提高-} /
AC记录:Accepted

题目大意

给你一个数列,你需要下面两种操作:

1.将数列中的某一个数加上 x x x
2.求出某区间每一个数的和

输入格式

第一行包含两个正整数 n , m n,m n,m,分别表示该数列数字的个数和操作的总个数。

第二行包含 n n n个用空格分隔的整数,其中第 i i i个数字表示数列第 i i i项的初始值。

接下来 m m m行每行包含 3 3 3个整数,表示一个操作,具体如下:

1 x k含义:将第 x x x个数加上 k k k
2 x y含义:输出区间 [ x , y ] \left[x,y\right] [x,y]内每个数的和

输出格式

输出包含若干行整数,即为所有操作 2 2 2的结果。

S a m p l e \mathbf{Sample} Sample I n p u t \mathbf{Input} Input

5 5
1 5 4 2 3
1 1 3
2 2 5
1 3 -1
1 4 2
2 1 4

S a m p l e \mathbf{Sample} Sample O u t p u t \mathbf{Output} Output

14
16

H i n t & E x p l a i n \mathbf{Hint\&Explain} Hint&Explain
Hint&Explain

数据范围

对于 30 % 30\% 30%的数据, 1 ≤ n ≤ 8 , 1 ≤ m ≤ 10 1\le n\le 8,1\le m\le 10 1n8,1m10
对于 70 % 70\% 70%的数据, 1 ≤ n , m ≤ 1 0 4 1\le n,m\le 10^4 1n,m104
对于 100 % 100\% 100%的数据, 1 ≤ n , m ≤ 5 × 1 0 5 1\le n,m\le 5\times 10^5 1n,m5×105

解题思路

前置知识

1. l o w b i t 运算 ( 不 会 戳 我 ) \color{red}1.lowbit\text{运算}\color{black}(不会戳我) 1.lowbit运算()


树状数组,是一种不可名状的神奇存储的数的方式。主要用于单点修改区间查询


存储方式

树状数组只用一个大小为 n n n的数组就可以运作,这里设他为 t r e e [ 1 ⋯ n ] tree\left[1\cdots n\right] tree[1n],当然,还有一个存储原来的数的数组 a [ 1 ⋯ n ] a\left[1\cdots n\right] a[1n]
t r e e [ x ] tree\left[x\right] tree[x]的含义则是在数组 a a a中以第 x x x个数为结尾的长度为 l o w b i t ( x ) lowbit(x) lowbit(x)的区间的和。
在这里插入图片描述


树状数组的更新

当某一个数被更新后,我们需要更新包含他的所有区间。设被更新的数是 a [ x ] a[x] a[x],加上了 k k k
我们可以这么想:一个数的 l o w b i t lowbit lowbit运算得到的是这个数最低位的 1 1 1所代表的数,那么加上他就最低位的 1 1 1一定会发生进位。而加上一个 l o w b i t ( x ) lowbit(x) lowbit(x) t r e e tree tree数组里面只是把区间扩大,所以 t r e e [ x + l o w b i t ( x ) ] tree\left[x+lowbit(x)\right] tree[x+lowbit(x)]这个区间肯定会包含 a [ x ] a[x] a[x],所以 t r e e [ x + l o w b i t ( x ) ] tree[x+lowbit(x)] tree[x+lowbit(x)]也要更新。
之后只要一直加上去就可以了。
如果对上面的过程不懂,可以参照存储方式里的图来进行理解。
E x a m p l e   c o d e Example\ code Example code

void update(int x,int pos)
{
    while(pos<=n)
    {
        tree[pos]+=x;
        pos+=lowbit(pos);
    }
    return;
}

树状数组的区间查询

在这里插入图片描述
注:element的翻译为元素

要解决区间查询,首先要解决一个子问题:查询 a [ 1 ⋯ x ] a[1\cdots x] a[1x]所代表的值的和。
类似上面的图,我们可以把 a [ 1 ⋯ x ] a[1\cdots x] a[1x]分成一个个长度为 2 k 2^k 2k的子区间。
由于树状数组的定义,我们可以直接调用 t r e e tree tree数组,再相加就可以了。
但问题是:要加哪一个 t r e e tree tree数组里的元素呢?
由于 l o w b i t ( x ) lowbit(x) lowbit(x)的值肯定是 2 k 2^k 2k,所以 l o w b i t ( x ) lowbit(x) lowbit(x)就可以作为子区间的长度。用 x − l o w b i t ( x ) x-lowbit(x) xlowbit(x),就可以去掉最低位上的 1 1 1,继续加值。
解决了 a [ 1 ⋯ x ] a[1\cdots x] a[1x] a [ x ⋯ y ] a[x\cdots y] a[xy]也很简单了,式子为:
a [ x ⋯ y ] = a [ 1 ⋯ y ] − a [ 1 ⋯ ( x − 1 ) ] a[x\cdots y]=a[1\cdots y]-a[1\cdots (x-1)] a[xy]=a[1y]a[1(x1)]
E x a m p l e   c o d e Example\ code Example code

inline int one_to_n(int pos)
{
    int tar=0;
    while(pos)
    {
        tar+=tree[pos];
        pos-=lowbit(pos);
    }
    return tar;
}

inline int section(int l,int r)
{
    return one_to_n(r)-one_to_n(l-1);
}

最后,祝大家早日
请添加图片描述

上代码

#include<iostream>
using namespace std;

typedef unsigned long long ull;
typedef long long ll;

#define int ll

int tree[500010];
int a[500010];
int n,m;

inline int lowbit(int num) //lowbit运算
{
    return num&(-num);
}

void update(int x,int pos) //更新
{
    while(pos<=n)
    {
        tree[pos]+=x;
        pos+=lowbit(pos);
    }
    return;
}

void init()
{
    for(int i=1; i<=n; i++)
        cin>>a[i],update(a[i],i);
    return;
}

inline int one_to_n(int pos) //a[1...n]
{
    int tar=0;
    while(pos)
    {
        tar+=tree[pos];
        pos-=lowbit(pos);
    }
    return tar;
}

inline int section(int l,int r) //a[l...r]
{
    return one_to_n(r)-one_to_n(l-1);
}

signed main()
{
    cin>>n>>m;
    init();
    for(int i=1; i<=m; i++)
    {
        int x;
        cin>>x;
        if(x==1)
        {
            int y,z;
            cin>>y>>z;
            update(z,y);
        }
        else
        {
            int y,z;
            cin>>y>>z;
            cout<<section(y,z)<<endl;
        }
    }
    return 0;
}

完美切题 ∼ \sim

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值