前言
做个总结,忘记之后再翻翻
首先明确一下树状数组的结构性质:
1.每个内部节点保存以它为根的子树中所有叶节点的和
2.每个内部节点的子节点个数等于的位数,即位为1的数量
例如:
,分为两个区间,区间和分别为
,分为三个区间,区间和分别为
3.除树根外,每个内部节点的父节点是
4.树的深度为。因此可以保证在的时间内查询前缀和
树状数组应用总结
主要分为三种:
- 单点修改,单点查询(最基础)
- 区间修改,单点查询
- 区间修改,区间查询
应用一:单点修改,单点查询
最基础的定义
查询
int ask(int x)
{
int ans=0;
for (;x;x-=x&-x)ans+=c[x];
return ans;
}
修改
void add(int x,int y)
{
for (;x<=n;x+=x&-x)c[x]+=y;
}
例题:POJ2182
题意:有头奶牛,身高不等,站成一列,已知第头奶牛前面有头比他矮,求出所有身高。
思路:从后往前思考。表示前头牛有头比最后一头矮,由于不存在重复身高,最后一头牛的身高肯定是排第位的,同理第头的身高一定是排第位,但是已经出现过的身高就得排除。可以通过维护一个初始全1的01序列,查询第个1的位置并且将其修改为0。用二分+树状数组即可
代码:
int n,A[maxn],c[maxn],ans[maxn];
int ask(int x){int ans=0;for(;x;x-=x&-x)ans+=c[x];return ans;}
void add(int x,int y){for(;x<=n;x+=x&-x)c[x]+=y;}
int solve(int x)
{
int l=1,r=n;
while(l<r)
{
int mid=(l+r)>>1;
if (ask(mid)<x)l=mid+1;
else r=mid;
}
return l;
}
int main()
{
scanf("%d",&n);
A[1]=0;rep(i,2,n)scanf("%d",&A[i]);
rep(i,1,n)add(i,1);
for (int i=n;i>=1;i--)
{
ans[i]=solve(A[i]+1);
add(ans[i],-1);
}
rep(i,1,n)W(ans[i]);
}
应用二:区间修改,单点查询
新建数组,前缀和~反映了指令"C l r d"对的影响
1.把
2.把
观察一下数组的变化:
1.当时:前缀和~无变化
2.当时:前缀和~增加了,因为增加了
3.当时:前缀和~,因为一加一减刚好抵消
也就是说 对于区间内的数的影响,都体现在了数组的前缀和中
而求前缀和就是树状数组的基础操作,累加上原先的即可得到"Q x"的答案
蓝书例题:
代码:
int n,m,l,r,x,a[maxn],c[maxn];
char s[5];
int ask(int x){int ans=0;for (;x;x-=x&-x)ans+=c[x];return ans;}
void add(int x,int y){for (;x<=n;x+=x&-x)c[x]+=y;}
int main()
{
scanf("%d%d",&n,&m);
rep(i,1,n)scanf("%d",&a[i]);
while(m--)
{
scanf("%s",s);
if (s[0]=='Q')
{
scanf("%d",&x);
W(ask(x)+a[x]);
}
else if (s[0]=='C')
{
scanf("%d%d%d",&l,&r,&x);
add(l,x);
add(r+1,-x);
}
}
}
应用三:区间修改,区间查询
首先仍是维护一个数组,变化量就是前缀和~
那么原数组的前缀和~变化量就是:
第一部分就是维护的前缀和
第二部分就是维护的前缀和,令
由于只是乘了个,故与之前对应的操作是:
1.把
2.把
观察一下数组的变化:
当时:前缀和~增加了,因为增加了
综上所述:的原始前缀和+的前缀和变化量即为最终的的前缀和
区间查询即输出
蓝书例题:
代码:
int n,m;
ll a[maxn],c1[maxn],c2[maxn],sum[maxn],l,r,x;
char s[5];
ll ask1(ll x){ll ans=0;for(;x;x-=x&-x)ans+=c1[x];return ans;}
ll ask2(ll x){ll ans=0;for(;x;x-=x&-x)ans+=c2[x];return ans;}
void add1(ll x,ll y){for(;x<=n;x+=x&-x)c1[x]+=y;}
void add2(ll x,ll y){for(;x<=n;x+=x&-x)c2[x]+=y;}
int main()
{
scanf("%d%d",&n,&m);
rep(i,1,n)scanf("%lld",&a[i]),sum[i]=sum[i-1]+a[i];
while(m--)
{
scanf("%s",s);
scanf("%lld%lld",&l,&r);
if (s[0]=='Q')
{
ll ans1=sum[r]-sum[l-1];
ll ans2=(r+1)*ask1(r)-l*ask1(l-1);
ll ans3=-ask2(r)+ask2(l-1);
WW(ans1+ans2+ans3);
}
else
{
scanf("%lld",&x);
add1(l,x);
add1(r+1,-x);
add2(l,l*x);
add2(r+1,-(r+1)*x);
}
}
}