树状数组
1.
先介绍一个函数:。它表示二进制下从右起第一个 的位置表示的数。例如:,故 。有
2. 树状数组例题 :【模板】树状数组 1
容易想到两种方法:
- 暴力
暴力修改每个数,每次暴力求和。 - 前缀和
每次修改 ,每次快速求和。
他们时间复杂度分别如下:
| 方法 | 修改 | 查询 |
|---|---|---|
| 暴力 | ||
| 前缀和 |
肯定都无法通过。考虑使用树状数组。
定义 。画个图就是这样:

这样子,便可以看到所有 的区间可以组合出来。这样就可以使用前缀和快速计算了。
现在的问题是:怎样快速维护 、求 。
- 维护
假设修改一个值 ,那么会需要改许多 。具体是哪些呢?首先, 是肯定有的。然后修改 ,然后继续修改便可。代码如下:
void add(int x,int k)
{
while(x<=n)
{
tr[x]+=k;
x+=lowbit(x);
}
}
- 求
要求的是 ,(易得)。故可以一直循环下去。代码如下:
int sum(int x)
{
int ans=0;
while(x!=0)
{
ans+=tr[x];
x-=lowbit(x);
}
return ans;
}
完整代码如下:
#include<bits/stdc++.h>
#define int long long
#define lowbit(x) (x&(-x))
using namespace std;
int tr[500006];
int n,m;
void add(int x,int k)
{
while(x<=n)
{
tr[x]+=k;
x+=lowbit(x);
}
}
int sum(int x)
{
int ans=0;
while(x!=0)
{
ans+=tr[x];
x-=lowbit(x);
}
return ans;
}
signed main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
int a;
cin>>a;
add(i,a);
}
while(m--)
{
int op,a,b;
cin>>op>>a>>b;
if(op==1) add(a,b);
else cout<<sum(b)-sum(a-1)<<endl;
}
return 0;
}
3. 树状数组例题 :【模板】树状数组 2
记 (即差分),则修改时,只需要修改 上的位置便可。
代码如下:
#include<bits/stdc++.h>
#define int long long
#define lowbit(x) (x&(-x))
using namespace std;
int tr[500006];
int s[500006];
int n,m;
void add(int x,int k)
{
while(x<=n)
{
tr[x]+=k;
x+=lowbit(x);
}
}
int sum(int x)
{
int ans=0;
while(x!=0)
{
ans+=tr[x];
x-=lowbit(x);
}
return ans;
}
signed main()
{
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>s[i];
while(m--)
{
int op,a,b,c;
cin>>op>>a;
if(op==1)
{
cin>>b>>c;
add(a,c);
add(b+1,-c);
}
else
{
cout<<s[a]+sum(a)<<endl;
}
}
return 0;
}
4. 树状数组例题 :Preprefix sum
这里牵涉到了单点查询和超级区间修改,不考虑树状数组。
先看一下要求的问题:求 。
展开看一下:
归纳一下:。提一下柿子:。
由于这两个柿子都能用树状数组维护,所以可以用树状数组来维护查询和修改。
修改代码:
add1(x,y-a[x]);add2(x,(y-a[x])*x);
查询代码:
ans=((x+1)*ask1(x)-ask2(x));

浙公网安备 33010602011771号