線段樹系列之——單點更新與區(qū)段總和

線段樹

線段樹是一種二叉搜索樹,與區(qū)間樹相似甜紫,它將一個區(qū)間劃分成一些單元區(qū)間,每個單元區(qū)間對應(yīng)線段樹中的一個葉結(jié)點昔期。

接下來的幾篇文章我可能會多總結(jié)一些關(guān)于線段樹的題目、擴展及其用法佛玄,幫助一些不太懂線段樹的ACMer和未來忘記線段樹的自己……

例題

題目鏈接

題意

中文題題意就不解釋了硼一。下面我們用糖果來進(jìn)行相關(guān)說明,桌上有n堆糖果梦抢,給你每堆糖果的初始數(shù)量般贼,然后進(jìn)行一些操作,這些操作可以往指定的糖果堆里放糖果奥吩,也可以往指定的糖果堆里拿走糖果哼蛆,也可以詢問第a~b堆糖果的總數(shù)是多少。對于每次詢問霞赫,輸出這幾堆糖果的總數(shù)

導(dǎo)學(xué)

可能讀者以前沒接觸過線段樹腮介,我借這道題形容一下。如果你用數(shù)組來維護(hù)這一堆糖果端衰,那么增加和減少可能比較方便叠洗,但是計算總和的時候每一次詢問操作都需要重新循環(huán),肯定就超時了

那么使用線段樹的好處在哪里呢旅东?比如10堆糖果灭抑,它把這線型的10堆糖果變成了樹型。整一棵樹的所有葉節(jié)點存儲的是每一堆糖果的數(shù)量抵代,而其葉節(jié)點的父節(jié)點則存儲了該節(jié)點下所有子節(jié)點的糖果數(shù)的總和腾节。依次往上,根節(jié)點存儲的也就是所有糖果堆的總和

增加/減少糖果時荤牍,找到該糖果所在的葉節(jié)點的位置案腺,更新,并且更新所有與該葉節(jié)點有關(guān)的“父節(jié)點”参淫,在查詢時救湖,就不需要重新計算了,將指定父節(jié)點的位置相加即可涎才。

解析

在這題中鞋既,首先建立一個空的線段樹,即假設(shè)每堆糖果的數(shù)量為0

void build(int l,int r,int p){ //構(gòu)造線段樹
    tree[p].left=l;   //節(jié)點所表示的范圍起點
    tree[p].right=r;  //節(jié)點所表示的范圍終點
    tree[p].value=0;  //糖果的數(shù)量初始置0
    if(l==r) return;
    int mid=(l+r)/2;
    build(l,mid,p*2);  //依次建立子節(jié)點耍铜,直到處理到葉節(jié)點為止
    build(mid+1,r,p*2+1);
}
……
cin>>n;   //糖果的堆數(shù)
build(1,n,1);

然后得到每一堆糖果的具體數(shù)量邑闺。對于每一堆的數(shù)量,其實就相當(dāng)于在原來置0的情況下棕兼,進(jìn)行依次增加操作陡舅,往第i堆中增加value個糖果。

void add(int k,int p,int value){
    if(tree[p].left==k&&tree[p].right==k){   //如果找到了對應(yīng)位置的葉子節(jié)點伴挚,就進(jìn)行增加操作
        tree[p].value+=value;
        return;
    }
    int mid=(tree[p].left+tree[p].right)/2;  //從子節(jié)點中尋找
    if(k<=mid) add(k,p*2,value);
    else add(k,p*2+1,value);
    tree[p].value = tree[2*p].value+tree[2*p+1].value;  //子節(jié)點更新了靶衍,父節(jié)點也要更新
}
……
for(i=1;i<=n;i++){
    scanf("%d",&num);
    add(i,1,num);
}

然后灾炭,在上述操作的基礎(chǔ)上,我們發(fā)現(xiàn)已經(jīng)解決了題目要求的增加和減少的命令了颅眶,因為減少n等于增加-n蜈出。還有最后一個查詢的操作,也就是查詢a~b堆的總數(shù)

void search(int l,int r,int p){
    if(tree[p].left==l&&tree[p].right==r){  //如果這個節(jié)點的范圍完全吻合所需要查找的范圍
        sum+=tree[p].value;  //直接增加
        return;   //加完就跑
    }
    //如果這個節(jié)點的范圍不滿足怎么辦涛酗?
    int mid = (tree[p].left+tree[p].right)/2;  //取當(dāng)前范圍的中點
    if(r<=mid) search(l,r,2*p);  //如果所求范圍在中點左邊铡原,往左子樹找
    else if(l>mid) search(l,r,2*p+1);  //如果所求范圍在中點右邊,往右子樹找
    else{
        //如果mid在所有范圍當(dāng)中商叹,也就是左子樹也有燕刻,右子樹也有,那就都找剖笙,但是需要處理一下范圍卵洗,不能直接傳遞l和r
        search(l,mid,2*p);
        search(mid+1,r,2*p+1);
    }
}
……
sum=0;
search(num1,num2,1);
cout<<sum<<endl;

上面的查詢操作也是這個代碼的核心操作,和數(shù)組查詢不同的是枯途,數(shù)組查詢每次都是在查詢?nèi)~節(jié)點忌怎,而線段樹幾乎不會到葉節(jié)點,總是到某個父節(jié)點就停下了酪夷,所以大大減少了操作次數(shù)

AC代碼

#include <iostream>
#include <string.h>
#include <stdio.h>
#define M 50005
using namespace std;
int sum;
struct node{
    int left;
    int right;
    int value;
}tree[4*M]; //大小為預(yù)定長度的4倍
void build(int l,int r,int p){ //構(gòu)造線段樹
    tree[p].left=l;   //節(jié)點所表示的范圍起點
    tree[p].right=r;  //節(jié)點所表示的范圍終點
    tree[p].value=0;  //糖果的數(shù)量初始置0
    if(l==r) return;
    int mid=(l+r)/2;
    build(l,mid,p*2);  //依次建立子節(jié)點,直到處理到葉節(jié)點位置
    build(mid+1,r,p*2+1);
}
void add(int k,int p,int value){
    if(tree[p].left==k&&tree[p].right==k){   //如果找到了對應(yīng)位置的葉子節(jié)點孽惰,就進(jìn)行增加操作
        tree[p].value+=value;
        return;
    }
    int mid=(tree[p].left+tree[p].right)/2;  //從子節(jié)點中尋找
    if(k<=mid) add(k,p*2,value);
    else add(k,p*2+1,value);
    tree[p].value = tree[2*p].value+tree[2*p+1].value;  //子節(jié)點更新了晚岭,父節(jié)點也要更新
}
void search(int l,int r,int p){
    if(tree[p].left==l&&tree[p].right==r){  //如果這個節(jié)點的范圍完全吻合所需要查找的范圍
        sum+=tree[p].value;  //直接增加
        return;   //加完就跑
    }
    //如果這個節(jié)點的范圍不滿足怎么辦?
    int mid = (tree[p].left+tree[p].right)/2;  //取當(dāng)前范圍的中點
    if(r<=mid) search(l,r,2*p);  //如果所求范圍在中點左邊勋功,往左子樹找
    else if(l>mid) search(l,r,2*p+1);  //如果所求范圍在終點右邊坦报,往右子樹找
    else{   //如果mid在所有范圍當(dāng)中,也就是左子樹也有狂鞋,右子樹也有片择,那就都找,但是需要處理一下范圍骚揍,不能直接傳遞l和r
        search(l,mid,2*p);
        search(mid+1,r,2*p+1);
    }
}
int main(){
    char str[10];
    int T,n,i,k,num,num1,num2;
    cin>>T;
    for(k=1;k<=T;k++){
        cin>>n;   //堆數(shù)
        build(1,n,1);
        for(i=1;i<=n;i++){
            scanf("%d",&num);
            add(i,1,num);
        }
        cout<<"Case "<<k<<":"<<endl;
        while(scanf("%s",str)&&strcmp(str,"End")!=0){
            scanf("%d%d",&num1,&num2);
            if(strcmp(str,"Add")==0)
                add(num1,1,num2);
            else if(strcmp(str,"Sub")==0)
                add(num1,1,-num2);
            else{
                sum=0;
                search(num1,num2,1);
                cout<<sum<<endl;
            }
        }
    }
    return 0;
}


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末字管,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子信不,更是在濱河造成了極大的恐慌嘲叔,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件抽活,死亡現(xiàn)場離奇詭異硫戈,居然都是意外死亡,警方通過查閱死者的電腦和手機下硕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門丁逝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來汁胆,“玉大人,你說我怎么就攤上這事霜幼∧勐耄” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵辛掠,是天一觀的道長谢谦。 經(jīng)常有香客問我,道長萝衩,這世上最難降的妖魔是什么回挽? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮猩谊,結(jié)果婚禮上千劈,老公的妹妹穿的比我還像新娘。我一直安慰自己牌捷,他們只是感情好墙牌,可當(dāng)我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著暗甥,像睡著了一般喜滨。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上撤防,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天虽风,我揣著相機與錄音,去河邊找鬼寄月。 笑死辜膝,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的漾肮。 我是一名探鬼主播厂抖,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼克懊!你這毒婦竟也來了忱辅?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤保檐,失蹤者是張志新(化名)和其女友劉穎耕蝉,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體夜只,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡垒在,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片场躯。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡谈为,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出踢关,到底是詐尸還是另有隱情伞鲫,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布签舞,位于F島的核電站秕脓,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏儒搭。R本人自食惡果不足惜吠架,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望搂鲫。 院中可真熱鬧傍药,春花似錦、人聲如沸魂仍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽擦酌。三九已至俱诸,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間赊舶,已是汗流浹背乙埃。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留锯岖,地道東北人。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓甫何,卻偏偏與公主長得像出吹,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子辙喂,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,762評論 2 345