关于树状数组
1.定义:
一种常用的数据结构,虽然树状数组的题线段树都能做,线段树的题树状数组不一定能做,但它代码比线段树短,思路更清晰易懂,并且在某些方面比线段树更好用。可以实现单点修改、单点查询、区间修改、区间查询四个操作
下图可反映树状数组的工作原理:
(转载自oi-wiki)
2.常用函数:
(1)lowbit:用来求一个数在二进制下最低位1的位置
inline int lowbit(int x)
{
return x&-x;
}
(2)add:用来做单点修改操作
inline void add(int x,int k)
{
for(register int i=x;i<=n;i+=lowbit(i))
c[i]+=k;
}
(3)sum:求前缀和,从而求区间和
inline int sum(int x)
{
int ans=0;
for(int i=x;i>=1;i-=lowbit(i))
ans+=c[i];
return ans;
}
典型例题
例一 P3374 【模板】树状数组1
树状数组的板子题,没什么好说的......
Code
#include <bits/stdc++.h>
using namespace std;
const int MAXN=5e5+5;
inline int read()
{
register int x=0,f=0;register char ch=getchar();
while(ch<'0' || ch>'9')f|=ch=='-',ch=getchar();
while(ch>='0' && ch<='9')x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return f?-x:x;
}
int c[MAXN];
int n,m,a[MAXN];
inline int lowbit(int x)
{
return x&-x;
}
inline void add(int x,int k)
{
for(register int i=x;i<=n;i+=lowbit(i))
c[i]+=k;
}
inline int sum(int x)
{
int ans=0;
for(register int i=x;i>=1;i-=lowbit(i))
ans+=c[i];
return ans;
}
int main()
{
n=read(),m=read();
for(register int i=1;i<=n;i++)
{
a[i]=read();
add(i,a[i]);
}
for(register int i=1;i<=m;i++)
{
int op=read();
if(op==1)
{
int x=read(),k=read();
add(x,k);
}
else
{
int x=read(),y=read();
printf("%d\n",sum(y)-sum(x-1));
}
}
return 0;
}
例二 P5057 简单题
这道题其实只要判断数的奇偶就行了,若模2为0则最后是0,反之最后是1,同样用树状数组维护
Code
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e5+5;
int n,m;
int c[MAXN];
inline int read()
{
register int x=0,f=0;register char ch=getchar();
while(ch<'0' || ch>'9')f|=ch=='-',ch=getchar();
while(ch>='0' && ch<='9')x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return f?-x:x;
}
inline int lowbit(int x)
{
return x&-x;
}
inline void add(int x)
{
for(;x!=0;x-=lowbit(x))
{
c[x]++;
}
}
inline int ask(int x)
{
int ans=0;
for(;x<=n;x+=lowbit(x))
{
ans+=c[x];
}
return ans;
}
int main()
{
n=read(),m=read();
for(register int i=1;i<=m;i++)
{
int op;
op=read();
if(op==1)
{
int x,y;
x=read(),y=read();
add(y),add(x-1);
}
else
{
int x;
x=read();
printf("%d\n",ask(x)%2);
}
}
return 0;
}
例三 P4378 Out of Sorts S
这题就排序+离散化就过了......
Code
#include <bits/stdc++.h>
using namespace std;
const int MAXN=1e5+5;
inline int read()
{
register int x=0,f=0;register char ch=getchar();
while(ch<'0' || ch>'9')f|=ch=='-',ch=getchar();
while(ch>='0' && ch<='9')x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return f?-x:x;
}
int n;
int a[MAXN],c[MAXN],p[MAXN];
inline int lowbit(int x)
{
return x&-x;
}
inline void add(int x)
{
for(;x<=n;x+=lowbit(x))
c[x]++;
}
inline int ask(int x)
{
int ans=0;
for(;x;x-=lowbit(x))
ans+=c[x];
return ans;
}
inline bool cmp(int x,int y)
{
return a[x]<a[y]||(a[x]==a[y]&&x<y);
}
int main()
{
n=read();
for(register int i=1;i<=n;i++)
{
scanf("%d",a+i);
p[i]=i;
}
sort(p+1,p+n+1,cmp);
for(register int i=1;i<=n;i++)
{
a[p[i]]=i;
}
int ans=1;
for(register int i=1;i<=n;i++)
{
add(a[i]);
ans=max(ans,ask(n)-ask(a[i]-1));
}
return !printf("%d\n",ans);
return 0;
}
例四 P2357 守墓人
这道题需要用差分来解决区间修改的问题。
这题有些不大一样,在推出的式子中,需要两个不同的树状数组去维护不同的东西,一个维护差分数组,一个维护差分数组乘下标的值,其他就和普通树状数组数组无异。
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN=2e5+5;
int n,f,c[MAXN],a[MAXN];
int v[MAXN];
inline int lowbit(int x)
{
return x&-x;
}
inline void add1(int x,int k)
{
for(register int i=x;i<=n;i+=lowbit(i))
c[i]+=k;
}
inline int sum1(int x)
{
int ans=0;
for(register int i=x;i>=1;i-=lowbit(i))
ans+=c[i];
return ans;
}
inline void add2(int x,int k)
{
for(register int i=x;i<=n;i+=lowbit(i))
v[i]+=k;
}
inline int sum2(int x)
{
int ans=0;
for(register int i=x;i>=1;i-=lowbit(i))
ans+=v[i];
return ans;
}
signed main()
{
scanf("%lld%lld",&n,&f);
for(register int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
add1(i,i*(a[i]-a[i-1]));
add2(i,a[i]-a[i-1]);
}
for(register int i=1;i<=f;i++)
{
int op;
scanf("%lld",&op);
if(op==1)
{
int l,r,k;
scanf("%lld%lld%lld",&l,&r,&k);
add2(l,k);
add2(r+1,-k);
add1(l,l*k);
add1(r+1,-(r+1)*k);
}
else if(op==2)
{
int k;
scanf("%lld",&k);
add2(1,k);
add2(2,-k);
add1(1,k);
add1(2,-k*2);
}
else if(op==3)
{
int k;
scanf("%lld",&k);
add2(1,-k);
add2(2,k);
add1(1,-k);
add1(2,k*2);
}
else if(op==4)
{
int l,r;
scanf("%lld%lld",&l,&r);
printf("%lld\n",(sum2(r)*(r+1)-sum1(r))-(sum2(l-1)*l-sum1(l-1)));
}
else
{
printf("%lld\n",v[1]);
}
}
return 0;
}
推荐练习题
T1 P3531 LIT-Letters
这道题要求使前后两个字符串相同,需要交换的次数,其实就是求逆序对的个数。
但是这里因为是字母,所以很明显需要离散化,将字母编号后再求逆序对。
Code
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN=1e7+5;
int c[MAXN];
int n;
char a[MAXN],b[MAXN];
int ans;
vector<int>v[30];
int t[30];
int p[MAXN];
inline int read()
{
register int x=0,f=0;register char ch=getchar();
while(ch<'0' || ch>'9')f|=ch=='-',ch=getchar();
while(ch>='0' && ch<='9')x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return f?-x:x;
}
inline int lowbit(int x)
{
return x&-x;
}
inline void add(int x,int k)
{
for(register int i=x;i<=n;i+=lowbit(i))
c[i]+=k;
}
inline int sum(int x)
{
int ans=0;
for(register int i=x;i>=1;i-=lowbit(i))
ans+=c[i];
return ans;
}
signed main()
{
n=read();
scanf("%s%s",a,b);
memset(t,-1,sizeof(t));
for(register int i=0;i<=n-1;i++)
{
v[b[i]-'A'+1].push_back(i+1);
}
for(register int i=0;i<=n-1;i++)
{
p[i+1]=v[a[i]-'A'+1][++t[a[i]-'A'+1]];
}
for(register int i=1;i<=n;i++)
{
add(p[i],1);
ans+=sum(n)-sum(p[i]);
}
printf("%lld",ans);
return 0;
}