A Simple Problem with Integers
Time Limit: 5000MS Memory Limit: 131072K
Total Submissions: 84089 Accepted: 26030
Case Time Limit: 2000MS
Description
You have N integers, A1, A2, … , AN. You need to deal with two kinds of operations. One type of operation is to add some given number to each number in a given interval. The other is to ask for the sum of numbers in a given interval.
Input
The first line contains two numbers N and Q. 1 ≤ N,Q ≤ 100000.
The second line contains N numbers, the initial values of A1, A2, … , AN. -1000000000 ≤ Ai ≤ 1000000000.
Each of the next Q lines represents an operation.
“C a b c” means adding c to each of Aa, Aa+1, … , Ab. -10000 ≤ c ≤ 10000.
“Q a b” means querying the sum of Aa, Aa+1, … , Ab.
Output
You need to answer all Q commands in order. One answer in a line.
Sample Input
10 5
1 2 3 4 5 6 7 8 9 10
Q 4 4
Q 1 10
Q 2 4
C 3 6 3
Q 2 4
Sample Output
4
55
9
15
题意:给n个数字,标号1到n。Q:输入两个数L R,查询区间[L,R]所有数字的和,C:输入三个数字L,R,c,区间[L,R]的所有数字加上c。
区间更新初试。
这是一道典型的区间更新求和的模版题。
区间更新查询过程(默认只有区间加减操作,id表示结点下标):
建树。
更新区间,更新的过程不是一直更新到最底层的结点,建树建好之后,每一个结点对应一个区间[l,r],需要更新的区间[L,R],把需要更新的区间划分成若干个小区间(可能是一个,可能是很多个),直到每一个子区间在树里面的结点上能找到对应区间。然后对树上的这个结点对应的sum[id]值进行加减操作,然后重要的就是还要对这个结点对应标记add[id]进行相同的操作,因为这个结点所有的子节点都没有进行更新,所以这个标记add[id]的意义就在于表示这个结点的所有子结点在需要时进行加减操作而且加减的数值add[id]。
遇到整个区间(某个结点对应的区间)已经进行过加减操作了,但要对它子区间进行操作,这时候add[id]的作用就有了,这时利用这个add[id]存的值对左右子区间(左右子树)的sum值进行更新,对子树add[id*2]和add[id*2+1]也进行相同的标记,表示子树的这个区间被操作过了,然后必须消除id这个结点对应的区间的标记,表示这个这个结点对应的区间未被操作过,向下查询或者更新的时候不需要对子结点进行操作。
查询函数,和更新函数操作类似,对需要查询的区间进行分割,直到所有子区间的值在树中能找到对应的区间然后累加起来就是答案,同样遇到有的区间被标记过就进行向下更新继续查询。
区间更新的三个主要函数: build();update();query();
build()函数与之前单点更新的建树过程差不多,只是多了对标记数组add[]的初始化。
update()函数,传入需要更新的区间[L,R],是否能被直接被查找到,不能的话就Push_Down()一下,就是向下进行更新,如果区间有标记的就行更新,区间没标记的就不会更新。然后将区间进行折半,中点为mid,当L <= mid时,说明mid左边有区间需要更新,就更新左边子树,mid < R,同理。然后子树更新完了要记得更新父亲结点。
query()函数,查询区间[L,R],能查找到就返回,不能查到到就Push_Down。直到查到到位置,当L <= mid时,说明mid左边有区间需要被查询,所以向下查询左子树区间。mid < R,同理。
代码:
#include"stdio.h"
#include"iostream"
#include"algorithm"
#include"string.h"
#define lson l,m,id<<1 ///我能把l写成1(笑脸),第一次发现1和l的区别,1的顶部朝下,l的顶部朝上
#define rson m+1,r,id<<1|1 ///以后还是id/2和id/2+1吧,<<>>傻傻分不清楚
typedef long long LL;
const int maxn = 100010;
using namespace std;
LL n,q;
LL add[maxn*4]; ///标记数组
LL sum[maxn*4]; ///区段和数组,l和r区间在函数中给
void Push_Up(LL id) ///向上更新(父亲结点)
{
sum[id] = sum[id*2]+sum[id*2+1];
}
void Push_Down(LL id,LL len) ///向下更新(左右子树)
{
if(add[id])
{
add[id<<1] += add[id]; ///对指定区间左右子树做标记
add[id<<1|1] += add[id];
sum[id<<1] += add[id]*(len-(len>>1)); ///对应区间加上改变的值(左子树区间长度>=右子树区间长度)
sum[id<<1|1] += add[id]*(len>>1);
add[id] = 0; ///更新了要去掉标记
}
}
void build(LL l,LL r,LL id) ///建树
{
add[id] = 0; ///每个标记初始化为0
if(l == r)
{
scanf("%lld",&sum[id]); ///直接在每个结点输入值,节约空间和时间
return ;
}
LL m = (l+r)/2;
build(lson);
build(rson);
Push_Up(id); ///建完子树向上更新父亲结点
}
///[L,R]为需要更新的区间,c为区间[L,r]加的值,[l,r]为每个结点(id)所包含区间
void update(LL L,LL R,LL c,LL l,LL r,LL id) ///指定区间更新
{
if(L <= l && r <= R) ///递归到[l,r]区间在[L,R]内,则更新标记和区间的和
{
sum[id] += c*(r-l+1);
add[id] += c;
return;
}
Push_Down(id,r-l+1); ///更新区间不符合if,则向下更新(即更新左右子树)
LL m = (l+r)/2;
if(L <= m) update(L,R,c,lson); ///待更新区间在mid左边存在
if(m < R) update(L,R,c,rson); ///待更新区间在mid右边存在
Push_Up(id); ///更新完子结点,再更新父亲结点
}
///[L,R]为需要查询的区间,[l,r]为每个结点(id)所包含区间
LL query(LL L,LL R,LL l,LL r,LL id) ///查询[L,R]和
{
if(L <= l && r <= R) ///遇到区间[l,r]在查询的[L,R]内,返回这一段区间的和
{
return sum[id];
}
Push_Down(id,r-l+1); ///[l,r]不在这一段内,这把更新推到左右子树继续查询
LL m = (l+r)/2;
LL ans = 0; ///区间和
if(L <= m) ans += query(L,R,lson); ///查询的区间在mid左边存在
if(m < R) ans += query(L,R,rson); ///查询的区间在mid右边存在
return ans;
}
int main(void)
{
while(scanf("%lld%lld",&n,&q) != EOF)
{
LL L,R,c;
build(1,n,1);
///printf("%lld\n",query(4,4,1,n,1));
for(LL i = 1;i <= q;i++)
{
char s[2];
scanf("%s",s);
if(s[0] =='Q')
{
scanf("%lld%lld",&L,&R);
printf("%lld\n",query(L,R,1,n,1));
}
else
{
scanf("%lld%lld%lld",&L,&R,&c);
update(L,R,c,1,n,1);
}
}
}
return 0;
}