原题链接
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
数据范围
对于
30
%
30\%
30%的数据,
1
≤
n
≤
8
,
1
≤
m
≤
10
1\le n\le 8,1\le m\le 10
1≤n≤8,1≤m≤10。
对于
70
%
70\%
70%的数据,
1
≤
n
,
m
≤
1
0
4
1\le n,m\le 10^4
1≤n,m≤104。
对于
100
%
100\%
100%的数据,
1
≤
n
,
m
≤
5
×
1
0
5
1\le n,m\le 5\times 10^5
1≤n,m≤5×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[1⋯n],当然,还有一个存储原来的数的数组
a
[
1
⋯
n
]
a\left[1\cdots n\right]
a[1⋯n]。
而
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[1⋯x]所代表的值的和。
类似上面的图,我们可以把
a
[
1
⋯
x
]
a[1\cdots x]
a[1⋯x]分成一个个长度为
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)
x−lowbit(x),就可以去掉最低位上的
1
1
1,继续加值。
解决了
a
[
1
⋯
x
]
a[1\cdots x]
a[1⋯x],
a
[
x
⋯
y
]
a[x\cdots y]
a[x⋯y]也很简单了,式子为:
a
[
x
⋯
y
]
=
a
[
1
⋯
y
]
−
a
[
1
⋯
(
x
−
1
)
]
a[x\cdots y]=a[1\cdots y]-a[1\cdots (x-1)]
a[x⋯y]=a[1⋯y]−a[1⋯(x−1)]
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 ∼