已知树状数组区间修改单点查询时tree数组中存放的是相邻差值,那么区间修改的基础我们已经有了,但只能支持单点查询,需要再构造区间查询功能,暴力累加是不可能考虑的,而树状数组的区间查询是利用前缀和实现的,tree数组中存储的是前缀和。所以我们需要在单点查询的基础上再构造一个前缀和。我们需要整理一下查询
第k个数 a[k]=tree[1]+tree[2]+tree[3]+tree[4]+...+tree[k]
第k的前缀和为 s[k]=a[k]+a[k-1]+....+a[1]
即为 s[k]=(tree[1]+tree[2]+...tree[k])+(tree[1]+tree[2]+...+tree[k-1])+...+(tree[1])
拆分合并即为 s[k]=tree[1]*k+tree[2]*(k-1)+....+tree[k]*1
虽然这是一个和的形式,但是无法直接构造,因为与当前位置无直接关系,需要构造一个。
转化成包含普通前缀和的形式
s[k]=k*(tree[k]+tree[k-1]+...+tree[1])-(tree[k]*(k-1)+tree[k-1]*(k-2)+tree[k-2]*(k-3)+....+tree[1]*0)
前部分可以直接使用tree数组查询,观察后部分为tree[i]*(i-1)的前缀和形式,所以另设置一个树状数组p来存储。
初始化为 add(i,(i-1)*y) y为原数组的差分即当前元素与上一个元素的差值即tree[y]
区间修改时无异 add(x,k*(y-1)) add(y+1,-k*y)
所以所求的区间(x,y)和为 【y*y的tree前缀和-(x-1)*(x-1tree的前缀和)】-【y的p前缀和-x的p前缀和】
(y*sum(y,tree)-(x-1)*sum(x-1,tree))-(sum(y,p)-sum(x-1,p))
实际上就是利用前缀和构造。不能就转化成前缀和形式
代码如下:
#include<stdio.h>
#include<string.h>
#include<math.h>
#include<algorithm>
#include<iostream>
using namespace std;
#define INF 0x3f3f3f3f
#define ll long long
#define per(i,a,b) for(int i=a;i<=b;++i)
ll int n,m,tree[2000010],p[2000010];
ll int lowbit(ll int k)
{
return k&(-k);
}
void add(ll int x,ll int k,int fag)
{
while(x<=n)
{
if(fag==1) tree[x]+=k;
else p[x]+=k;
x+=lowbit(x);
}
}
ll int sum(ll int x,int fag)
{
ll int s=0;
while(x!=0)
{
if(fag==1) s+=tree[x];
else s+=p[x];
x-=lowbit(x);
}
return s;
}
int main()
{
scanf("%lld%lld",&n,&m);
ll int x,y=0;
per(i,1,n)
{
//ll int x,y;
scanf("%lld",&x);
y=x-y;
add(i,y,1);//区间查询单点修改,前缀和为该位置的值
add(i,(i-1)*y,2);//前缀和差值部分
y=x;
}
per(i,1,m)
{
ll int a,b,c,d;
scanf("%lld",&a);
if(a==1)
{
scanf("%lld%lld%lld",&b,&c,&d);
add(b,d,1);add(c+1,-d,1);//正常的插入
add(b,d*(b-1),2);add(c+1,-d*c,2);//正常的维护
}
if(a==2)
{
scanf("%lld%lld",&b,&c);
printf("%lld\n",(c*sum(c,1)-(b-1)*sum(b-1,1))-(sum(c,2)-sum(b-1,2)));
}
}
return 0;
}