欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

北京大学OpenJudge 3243 A Simple Problem with Integers (线段树区间求和)

程序员文章站 2022-05-02 11:34:01
...

描述

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 的数的和

          按照课件上的操作,我们可以这样做:

北京大学OpenJudge 3243 A Simple Problem with Integers (线段树区间求和)

 

北京大学OpenJudge 3243 A Simple Problem with Integers (线段树区间求和)

在增加时,如果要加的区间正好覆盖一个节点,则增加其节点的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))