题目描述
如题,已知一个数列,你需要进行下面两种操作:
1.将某区间每一个数加上x
2.求出某区间每一个数的和
输入输出格式
输入格式:
第一行包含两个整数N、M,分别表示该数列数字的个数和操作的总个数。
第二行包含N个用空格分隔的整数,其中第i个数字表示数列第i项的初始值。
接下来M行每行包含3或4个整数,表示一个操作,具体如下:
操作1: 格式:1 x y k 含义:将区间[x,y]内每个数加上k
操作2: 格式:2 x y 含义:输出区间[x,y]内每个数的和
输出格式:
输出包含若干行整数,即为所有操作2的结果。
输入输出样例
输入样例#1:
5 5
1 5 4 2 3
2 2 4
1 2 3 2
2 3 4
1 1 5 1
2 1 4
输出样例#1:
11
8
20
说明
时空限制:1000ms,128M
数据规模:
对于30%的数据:N<=8,M<=10
对于70%的数据:N<=1000,M<=10000
对于100%的数据:N<=100000,M<=100000
(数据已经过加强^_^,保证在int64/long long数据范围内)
【题解】
区间修改+区间查询
博主这里讲(主要还是博主自己的回顾)一下线段树和树状数组的做法
【线段树】
线段树还是很好理解的,只是博主也有一段时间没码过了,今天花了点时间码,博主总共错了三次
- 用的c++,然后编好后出现了玄学错误(至少我是这样认为的。。),c++的码力还是不行啊,所以我改用了pascal
- 既然是区间每个数加一个值,那么区间总和应该加区间长*delta,博主在update和传lazytag时都忘了区间长
- 忘开int64(long long)了,wa3个点
Code:
type
Node=record
num,tag:int64;
end;
var
tree:array[0..1000000] of Node;
a:array[0..1000000] of int64;
n,m,x,y,z,i:longint;
procedure build(root,l,r:longint);
var
mid:longint;
begin
if l=r then
begin
tree[root].num:=a[l];
exit;
end;
mid:=(l+r)>>1;
build(root<<1,l,mid); build(root<<1+1,mid+1,r);
tree[root].num:=tree[root<<1].num+tree[root<<1+1].num;
end;
procedure pushdown(root,l,r:longint);
var
mid:longint;
begin
mid:=(l+r)>>1;
inc(tree[root<<1].num,(mid-l+1)*tree[root].tag);
inc(tree[root<<1+1].num,(r-mid)*tree[root].tag);
inc(tree[root<<1].tag,tree[root].tag);
inc(tree[root<<1+1].tag,tree[root].tag);
tree[root].tag:=0;
end;
procedure update(root,tl,tr,l,r,delta:longint);
var
mid:longint;
begin
if (tl>r) or (tr<l) then exit;
if (tl>=l) and (tr<=r) then
begin
inc(tree[root].num,(tr-tl+1)*delta);
inc(tree[root].tag,delta);
exit;
end;
if (tree[root].tag>0) then pushdown(root,tl,tr);
mid:=(tl+tr)>>1;
update(root<<1,tl,mid,l,r,delta);
update(root<<1+1,mid+1,tr,l,r,delta);
tree[root].num:=tree[root<<1].num+tree[root<<1+1].num;
end;
function query(root,tl,tr,l,r:longint):int64;
var
mid:longint;
begin
if (tl>r) or (tr<l) then exit(0);
if (tl>=l) and (tr<=r) then exit(tree[root].num);
if (tree[root].tag>0) then pushdown(root,tl,tr);
mid:=(tl+tr)>>1;
query:=0;
inc(query,query(root<<1,tl,mid,l,r)+query(root<<1+1,mid+1,tr,l,r));
end;
begin
readln(n,m);
for i:=1 to n do read(a[i]);
build(1,1,n);
for i:=1 to m do
begin
read(x);
if x=1 then
begin
readln(x,y,z);
update(1,1,n,x,y,z);
end else
begin
readln(x,y);
writeln(query(1,1,n,x,y));
end;
end;
end.
【树状数组】
要用到差分的
若a[i]为序列中各个元素
我们令delta[i]=a[i]-a[i-1],那么有a[i]=delta[1]+delta[2]+delta[3]+···+delta[i-1]+delta[i]
那么此题,我们将初始数据与更新数据分开来,先维护一个初始前缀和sum[i]=sum[1]+sum[2]+sum[3]+···+sum[i-1]+sum[i],delta记录更新的数据,即delta[1]+delta[2]+delta[3]+···+delta[i]表示第i个数更新了多少
所以,1~i的和为:
sum[i]+i*delta[1]+(i-1)*delta[2]+(i-2)*delta[3]+···+2*delta[i-1]+delta[i]
=sum[i]+sigma(delta[x]*(i-x+1))
=sum[i]+(i-1)*sigma(delta[x])+sigma(delta[x]*x)
再令delta1[i]=delta[i]*i,那么此题就搞定了
Code:
type
ar=array[0..100000] of int64;
var
sum,delta,delta1:ar;
l,r,x,y,i,n,m:longint;
function lowbit(x:int64):int64;
begin
exit(x and -x);
end;
procedure change(var delta:ar;x,y:int64);
begin
while x<=n do
begin
inc(delta[x],y);
inc(x,lowbit(x));
end;
end;
function getsum(var delta:ar;x:int64):int64;
begin
getsum:=0;
while x>0 do
begin
inc(getsum,delta[x]);
dec(x,lowbit(x));
end;
end;
begin
readln(n,m);
for i:=1 to n do
begin
read(x);
sum[i]:=sum[i-1]+x;
end;
for i:=1 to m do
begin
read(x);
if x=1 then
begin
readln(l,r,x);
change(delta,l,x);
change(delta,r+1,-x); //delta的差分
change(delta1,l,x*l);
change(delta1,r+1,-x*(r+1)); //delta1的差分
end else
begin
readln(l,r);
writeln(sum[r]-sum[l-1]+(r+1)*getsum(delta,r)-l*getsum(delta,l-1)-getsum(delta1,r)+getsum(delta1,l-1));
end;
end;
end.
Ps:树状数组的Code是一年前的老程序了(博主懒得再码了),码风略诡异,望谅解~~~