做个总结,忘记之后再翻翻
首先明确一下树状数组的结构性质:
例如:
x = 9 = 2 3 + 2 1 x=9=2^3+2^1 x=9=23+21,分为 [ 1 , 8 ] , [ 9 , 9 ] [1,8],[9,9] [1,8],[9,9]两个区间,区间和分别为 c [ 8 ] , c [ 9 ] c[8],c[9] c[8],c[9]
x = 13 = 2 3 + 2 2 + 2 0 x=13=2^3+2^2+2^0 x=13=23+22+20,分为 [ 1 , 8 ] , [ 9 , 12 ] , [ 13 , 13 ] [1,8],[9,12],[13,13] [1,8],[9,12],[13,13]三个区间,区间和分别为 c [ 8 ] , c [ 12 ] , c [ 13 ] c[8],c[12],c[13] c[8],c[12],c[13]
主要分为三种:
最基础的定义
查询
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
题意:有 n n n头奶牛,身高 1 − n 1-n 1−n不等,站成一列,已知第 i i i头奶牛前面有 A i A_i Ai头比他矮,求出所有身高。
思路:从后往前思考。 A n A_n An表示前 n − 1 n-1 n−1头牛有 A n A_n An头比最后一头矮,由于不存在重复身高,最后一头牛的身高肯定是排第 A n + 1 A_n+1 An+1位的,同理第 i i i头的身高一定是排第 A i + 1 A_i+1 Ai+1位,但是已经出现过的身高就得排除。可以通过维护一个初始全1的01序列,查询第 k k k个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]);
}
新建数组 b b b,前缀和 b [ 1 b[1 b[1~ x ] x] x]反映了指令"C l r d"对 a [ x ] a[x] a[x]的影响
1.把 b [ l ] + d b[l]+d b[l]+d
2.把 b [ r + 1 ] − d b[r+1]-d b[r+1]−d
观察一下数组 b b b的变化:
1.当 1 ≤ x < l 1\leq x< l 1≤x<l时:前缀和 b [ 1 b[1 b[1~ x ] x] x]无变化
2.当 l ≤ x ≤ r l \leq x \leq r l≤x≤r时:前缀和 b [ 1 b[1 b[1~ x ] x] x]增加了 d d d,因为 b [ l ] b[l] b[l]增加了 d d d
3.当 r < x ≤ n r< x \leq n r<x≤n时:前缀和 b [ 1 b[1 b[1~ x ] x] x],因为一加一减刚好抵消
也就是说 对于区间 [ l , r ] [l,r] [l,r]内的数的影响,都体现在了数组 b b b的前缀和中
而求前缀和就是树状数组的基础操作,累加上原先的 a [ x ] a[x] a[x]即可得到"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);
}
}
}
首先仍是维护一个数组 b b b, a [ x ] a[x] a[x]变化量就是前缀和 b [ 1 b[1 b[1~ x ] x] x]
那么原数组 a a a的前缀和 a [ 1 a[1 a[1~ x ] x] x]变化量就是:
∑ i = 1 x ∑ j = 1 i b [ j ] = ( x + 1 ) ∑ i = 1 x b [ i ] − ∑ i = 1 x ( i × b [ i ] ) \sum_{i=1}^x\sum_{j=1}^i b[j]=(x+1)\sum_{i=1}^x b[i]-\sum_{i=1}^x (i×b[i]) ∑i=1x∑j=1ib[j]=(x+1)∑i=1xb[i]−∑i=1x(i×b[i])
第一部分就是维护 b b b的前缀和
第二部分就是维护 i × b [ i ] i×b[i] i×b[i]的前缀和,令 i × b [ i ] = c 2 [ x ] i×b[i]=c2[x] i×b[i]=c2[x]
由于只是乘了个 i i i,故与之前对应的操作是:
1.把 c 2 [ l ] + l d c2[l]+ld c2[l]+ld
2.把 c 2 [ r + 1 ] − ( r + 1 ) d c2[r+1]-(r+1)d c2[r+1]−(r+1)d
观察一下数组 b b b的变化:
当 l ≤ x ≤ r l \leq x \leq r l≤x≤r时:前缀和 c 2 [ 1 c2[1 c2[1~ x ] x] x]增加了 l d ld ld,因为 c 2 [ l ] c2[l] c2[l]增加了 l d ld ld
综上所述: a a a的原始前缀和+ a a a的前缀和变化量即为最终的 a a a的前缀和
S u m [ r ] = s u m [ r ] + ( r + 1 ) ∑ i = 1 r b [ i ] − ∑ i = 1 r ( i × b [ i ] ) Sum[r]=sum[r]+(r+1)\sum_{i=1}^r b[i]-\sum_{i=1}^r (i×b[i]) Sum[r]=sum[r]+(r+1)∑i=1rb[i]−∑i=1r(i×b[i])
= s u m [ r ] + ( r + 1 ) a s k 1 ( r ) − a s k 2 ( r ) =sum[r]+(r+1)ask1(r)-ask2(r) =sum[r]+(r+1)ask1(r)−ask2(r)
区间查询即输出 S u m [ r ] − S u m [ l − 1 ] Sum[r]-Sum[l-1] Sum[r]−Sum[l−1]
蓝书例题:
代码:
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);
}
}
}