最一般树状数组能做到的操作是单点修改,区间求和,都是log(n)级别的。原理就是用树状数组维护a[i]的部分和。
想要做到修改区间,求单点值也很简单,用树状数组维护a[i]的差分数组d[i]的部分和既可。
那么,如何同时做到区间求和,区间修改呢?
有人可能会说了,如果是区间求和区间修改的话,直接写线段树不就好了吗?
但是,从代码长度来看(dalao请无视),显然使用树状数组在考场上出错率会小一些,而且比较爽QWQ。
回归正题。我们知道,想要达到log(n)级别的区间修改需要修改d[i],那么在修改d[i]的情况下想要区间求和,我们就应该维护这个:
找出d[i]与a[i]的前缀和的关系,并用树状数组维护这个关系。
那么如何找关系呢?这里需要一点点数学证明:
我们设sum(i)=a[1]+a[2]+a[3]+······+a[i],
则可知
sum(i)=(d[1])+(d[1]+d[2])+(d[1]+d[2]+d[3])+······+(d[1]+d[2]+d[3]+······+d[i]),
所以
sum(i)=i*d[1]+(i-1)*d[2]+(i-2)*d[3]+······+1*d[i],
所以
sum(i)=∑(j从1到i)(i-j+1)d[j],
最后一步化简后,我们得到这样一个式子:
sum(i)=(i+1)∑(j从1到i)d[j]-∑(j从1到i)j*d[j]
即我们需要用树状数组分别维护d[i]的和和i*d[i]的和既可。
下面上代码:
1#include <iostream> 2#include <cstdio> 3#include <cstdlib> 4#include <algorithm> 5using namespace std; 6 7long long n,m; 8long long c1[100010],c2[100010]; 910void update(long long x,long long k)11{12 long long i=x;13 while(x<=n)14 {15 c1[x]+=k;16 c2[x]+=i*k;17 x+=x&-x;18 }19 return;20}2122long long sum(long long x)23{24 long long ans=0;25 long long i=x;2627 while(x>0)28 {29 ans+=c1[x]*(i+1);30 ans-=c2[x];31 x-=x&-x;32 }33 return ans;34}3536int main()37{38 long long ord,x,y,k;39 long long a,b=0;4041 scanf("%lld%lld",&n,&m);42 for(long long i=1;i<=n;i++)43 {44 scanf("%lld",&a);45 update(i,a-b);46 b=a;47 }48 for(long long i=1;i<=m;i++)49 {50 scanf("%lld",&ord);51 if(ord-1)52 {53 scanf("%lld%lld",&x,&y);54 printf("%lld\n",sum(y)-sum(x-1));55 }56 else57 {58 scanf("%lld%lld%lld",&x,&y,&k);59 update(x,k);60 update(y+1,-k);61 }62 }63 return 0;64}
原文地址:https://www.cnblogs.com/CYHeimu/p/11058205.html
时间: 2024-11-10 05:36:17