描述
N個整數,
一種操作是在給定的區間,給區間內的每個數加上某個數。
另一種是在給定的區間求區間內的數的和。
輸入
第一行是兩個數,N,Q,
第二行是N個數,
接下來的Q行,每行代表一個操作:
“C a b c”表示加上c到
“Q a b” 表示查詢
輸出
按照順序輸出Q行的值,每行一個值。
輸入樣例
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
注意:總和可能超過32bit整數範圍
參考
1、線段樹,結構體內不含左右端點:http://www.cnblogs.com/TenosDoIt/p/3453089.html
2、線段樹,結構體內含左右端點:http://www.cnblogs.com/Inkblots/p/4919193.html
思路
參考2中的一段話比較重要:
要將數組s[]從[i,j]段上的元素均加上b,那麼我們通常需要遍歷每個元素( s[i], s[i+1], … ,s[j] )並+b,此時使用的操作數爲( j - i + 1 )次,但如果我們在某些情況下只關心[ i, j ]段內的總和呢,此時我們只需在[ i, j ]段內總和sum的基礎上+b*( j - i + 1 )就行了,這樣的操作數只需要一次。再者,若想知道[ i, j ]段內的和,直接輸出此前存儲的總和sum,這樣比每次查詢時都要遍歷( j - i + 1 )個元素要好得多。
線段樹設置延遲標記;
(1)構建時,非葉節點是其子樹的和;
(2)更新時,若區間匹配,則只更新根代表的區間標記和值,否則從根向下傳遞,更新全部的標記和真實值,並將根標記重置;
(3)查詢時,若區間匹配,則直接返回(因爲該區間已經完全更新),否則以該節點向下傳遞更新直到區間匹配,每次累加。
C++實現
6804K 4672MS
#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
const int MAXN = 100001;
int n,q; // 原始數據量,操作量
int s[MAXN];// 原始數據,下標從1開始
long long ans; // 查詢結果
struct Node{
int l, r; // 節點所表示區間的左右端點,對應的原始數組下標
ll val; // 節點本身的值
ll addVal; // 延遲標記,待增加的值
}tree[MAXN*4];//最少2倍,最多4倍(按照滿二叉樹的空間)
/**
構建線段樹
x:當前根下標
l,r:當前區間左右端點下標
(下標爲對應的原始數組s中的下標)
*/
void build(int x, int l, int r)
{
tree[x].l = l;
tree[x].r = r;
tree[x].addVal = 0;//延遲標記,初始化
if(l == r){//葉子節點
tree[x].val = s[l];
return;
}
int mid = (l + r) / 2;
//原始數據下標從1開始
build(2 * x, l, mid);
build(2 * x + 1, mid + 1, r);
tree[x].val = tree[2 * x].val + tree[2 * x + 1].val;//當前根是當前左右孩子的和,用於求和
}
/**
更新線段樹,從x向下更新,每個節點+m
x:當前根節點下標
l,r:待更新區間左右端點下標
*/
void update(int x, int l, int r, int m)
{
// 當前根更新,未更新標記,值更新完畢,
tree[x].val += m * (r - l + 1);
// 當前根的區間與待更新區間匹配,當前根更新標記,當前值與標記均更新完
if(tree[x].l == l && tree[x].r == r){
tree[x].addVal += m;
return;
}
// 區間未更新完全,向下更新所有的標記和值,更新完成後重置當前標記(由於可能有上次更新未向下傳遞,所以是累加)
if(tree[x].addVal){
tree[2 * x].addVal += tree[x].addVal;
tree[2 * x + 1].addVal += tree[x].addVal;
tree[2 * x].val += tree[x].addVal * (tree[2 * x].r - tree[2 * x].l + 1);
tree[2 * x + 1].val += tree[x].addVal * (tree[2 * x + 1].r - tree[2 * x + 1].l + 1);
tree[x].addVal = 0;
}
// 查找
int mid = (tree[x].r + tree[x].l) / 2;
// 待更新區間在當前根代表區間的左側,則更新左側
if(r <= mid){
update(2 * x, l, r, m);
}
// 待更新區間在當前根代表區間的右側,則更新右側
else if (l > mid){
update(2 * x + 1, l, r, m);
}
// 待更新區間橫跨mid
else{
update(2 * x, l, mid, m);
update(2 * x + 1, mid + 1, r, m);
}
}
/**
查詢
x:當前根下標
l,r:查詢區間左右端點下標
*/
void query(int x, int l, int r)
{
// 區間匹配,返回值, 全局變量ans遞歸調用累加,主程序查詢後要重置
if(tree[x].l == l && tree[x].r == r){
ans += tree[x].val;
return;
}
// 區間不匹配,向下更新標記和值,更新完成後重置標記
if(tree[x].addVal){
tree[2 * x].addVal += tree[x].addVal;
tree[2 * x + 1].addVal += tree[x].addVal;
tree[2 * x].val += tree[x].addVal * (tree[2 * x].r - tree[2 * x].l + 1);
tree[2 * x + 1].val += tree[x].addVal * (tree[2 * x + 1].r - tree[2 * x + 1].l + 1);
tree[x].addVal = 0;
}
// 更新完成後,繼續向下查詢
int mid = (tree[x].l + tree[x].r) / 2;
// 在左側
if(r <= mid)
{
query(2 * x, l, r);
}
// 在右側
else if(l > mid)
{
query(2 * x + 1, l, r);
}
// 橫跨
else
{
query(2 * x, l, mid);
query(2 * x + 1, mid + 1, r);
}
}
int main()
{
//freopen("in.txt", "r", stdin);
cin >> n >> q;
for(int i = 1; i <= n; ++i)
{
cin >> s[i];
}
build(1, 1, n);
while(q--)
{
char oper;
int low, high, delta;
cin >> oper;
if(oper == 'C'){
cin >> low >> high >> delta;
update(1, low, high, delta);
}
else if(oper == 'Q'){
ans = 0;
cin >> low >> high;
query(1, low, high);
cout << ans << endl;
}
}
//fclose(stdin);
return 0;
}