POJ 3468 線段樹

描述

N個整數,A1,A2,,AN . 需要處理兩種操作:
一種操作是在給定的區間,給區間內的每個數加上某個數。
另一種是在給定的區間求區間內的數的和。

輸入

第一行是兩個數,N,Q,1N,Q100000 .
第二行是N個數,A1,A2,,AN 的初始值, 1,000,000,000Ai1,000,000,000 .
接下來的Q行,每行代表一個操作:
“C a b c”表示加上c到Aa,Aa+1,,Ab.10000c10000.
“Q a b” 表示查詢 Aa,Aa+1,,Ab 的和。

輸出

按照順序輸出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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章