北京大学OpenJudge 3243 A Simple Problem with Integers (线段树区间求和)
描述
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.
输入
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.
输出
You need to answer all Q commands in order. One answer in a line.
样例输入
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
样例输出
4 55 9 15
提示
The sums may exceed the range of 32-bit integers.
来源
POJ Monthly--2007.11.25, Yang Yi
Source File
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int nCount=0;
int n,q,a,b,c;
struct CNode{
int L,R;
CNode *pLeft,*pRight;
long long nSum;//原来的和
long long lnc;
};
CNode Tree[200010];
void BuildTree(CNode *pRoot,int L,int R){
pRoot->L=L;
pRoot->R=R;
pRoot->nSum=0;
pRoot->lnc=0;
if(L==R)
return ;
int mid=(L+R)/2;
nCount++;
pRoot->pLeft=Tree+nCount;
nCount++;
pRoot->pRight=Tree+nCount;
BuildTree(pRoot->pLeft,L,mid);
BuildTree(pRoot->pRight,mid+1,R);
}
void Insert(CNode *pRoot,int i,int v){
pRoot->nSum+=v;
if(pRoot->L==pRoot->R)
return ;
int mid=(pRoot->L+pRoot->R)/2;
if(i<=mid)
Insert(pRoot->pLeft,i,v);
else
Insert(pRoot->pRight,i,v);
}
void Add(CNode *pRoot,int a,int b,long long c){
if(pRoot->L==a&&pRoot->R==b){
pRoot->lnc+=c;
return ;
}
pRoot->nSum+=c*(b-a+1);
int mid=(pRoot->L+pRoot->R)/2;
if(b<=mid)
Add(pRoot->pLeft,a,b,c);
else if(a>mid)
Add(pRoot->pRight,a,b,c);
else{
Add(pRoot->pLeft,a,mid,c);
Add(pRoot->pRight,mid+1,b,c);
}
}
long long QuerynSum(CNode *pRoot,int a,int b){
if(pRoot->L==a&&pRoot->R==b)
return pRoot->nSum+(b-a+1)*pRoot->lnc;
int mid=(pRoot->L+pRoot->R)/2;
pRoot->nSum+=(pRoot->R-pRoot->L+1)*pRoot->lnc;
Add(pRoot->pLeft,pRoot->L,mid,pRoot->lnc);
Add(pRoot->pRight,mid+1,pRoot->R,pRoot->lnc);
pRoot->lnc=0;
if(b<=mid)
return QuerynSum(pRoot->pLeft,a,b);
else if(a>mid)
return QuerynSum(pRoot->pRight,a,b);
else{
return QuerynSum(pRoot->pLeft,a,mid)+QuerynSum(pRoot->pRight,mid+1,b);
}
}
int main(){
cin>>n>>q;
BuildTree(&Tree[0],1,n);
for(int i=1;i<=n;i++){
cin>>a;
Insert(&Tree[0],i,a);
}
for(int i=0;i<q;i++){
string cmd;
cin>>cmd;
if(cmd[0]=='C'){
cin>>a>>b>>c;
Add(Tree,a,b,c);
}
else{
cin>>a>>b;
cout<<QuerynSum(Tree,a,b)<<endl;
}
}
return 0;
}
思路:这道题最大的问题在于总是Presentation Error,这是真的伤不起。
简单说一下这道题的思路:
给定Q (1 ≤ Q ≤ 100,000) 个数A 1 ,A 2 … A Q , ,
以及可能多次进行的两个操作:
1) 对某个区间A i … A j 的 每个数 都加n(n 可变)
2) 求某个区间A i … A j 的数的和
按照课件上的操作,我们可以这样做:
在增加时,如果要加的区间正好覆盖一个节点,则增加其节点的Inc 值,不再往下走,否则要更新nSum( 加上本次增量), 再将增
量往下传。这样更新的复杂度就是O(log(n))
这里,我们是执行C 1 7 2和 Q 2 6的过程。
先说C 1 7 2
1,7这个区间并不和1,9吻合,我们先记录nSum值为(7-1+1)*2=14
再向下寻找,(1,5)这个区间吻合,我们就记录它们的增量为2
再看(6,7)这个区间一开始和(6,9)并不吻合,我们记录nSum为(7-6+1)*2=4
(6,7)在下一层吻合,记录增量Inc=2
这个时候C操作结束,不做输出。
而Q操作中,其实就是区间分解的过程。在查询时,如果待查区间不是正好覆盖一个节点,就将节点的Inc 往下带,然后将Inc代表的所有增量累加到nSum 上后将Inc 清0,接下来再往下查询。 一边查询,一边 Inc 往下带的过程也是区间分解的过程,复杂度也是O(log(n))
上一篇: 【软件工程】结对编程总结