1. 程式人生 > >數據結構的半夜——線段樹學習筆記1

數據結構的半夜——線段樹學習筆記1

rip 需要 處理 i++ 結束 -i nta 包含 dbr

說過以後寫blog要嚴肅點,我現在就嚴肅地修改一下,刪冗余,精簡語言

線段樹,英文Segment Tree

這種樹形數據結構十分容易形象地繪成圖形

技術分享圖片

如圖所示,線段樹有以下性質:

  1. 線段樹本質是棵二叉樹.
  2. 線段樹每個節點是代表一個區間[l,r],而每個節點的兩個子樹分別[l,mid],[mid+1,r]。
  3. 葉子節點l等於r且長度為1
  4. 線段樹深度不超過logn

綜上所述,我們可以按二叉樹的建樹方法,若節點編號為th,令節點的左兒子編號為th x 2,而右兒子記為th x 2+1。

kuanbin帶你飛的前三道例題,是非常好的線段樹入門例題,可以借此來初步理解線段樹以及線段樹的基本應用

A-敵兵布陣 HDU-1166

C國的死對頭A國這段時間正在進行軍事演習,所以C國間諜頭子Derek和他手下Tidy又開始忙乎了。A國在海岸線沿直線布置了N個工兵營地,Derek和Tidy的任務就是要監視這些工兵營地的活動情況。由於采取了某種先進的監測手段,所以每個工兵營地的人數C國都掌握的一清二楚,每個工兵營地的人數都有可能發生變動,可能增加或減少若幹人手,但這些都逃不過C國的監視。
中央情報局要研究敵人究竟演習什麽戰術,所以Tidy要隨時向Derek匯報某一段連續的工兵營地一共有多少人,例如Derek問:“Tidy,馬上匯報第3個營地到第10個營地共有多少人!”Tidy就要馬上開始計算這一段的總人數並匯報。但敵兵營地的人數經常變動,而Derek每次詢問的段都不一樣,所以Tidy不得不每次都一個一個營地的去數,很快就精疲力盡了,Derek對Tidy的計算速度越來越不滿:"你個死肥仔,算得這麽慢,我炒你魷魚!”Tidy想:“你自己來算算看,這可真是一項累人的工作!我恨不得你炒我魷魚呢!”無奈之下,Tidy只好打電話向計算機專家Windbreaker求救,Windbreaker說:“死肥仔,叫你平時做多點acm題和看多點算法書,現在嘗到苦果了吧!”Tidy說:"我知錯了。。。"但Windbreaker已經掛掉電話了。Tidy很苦惱,這麽算他真的會崩潰的,聰明的讀者,你能寫個程序幫他完成這項工作嗎?不過如果你的程序效率不夠高的話,Tidy還是會受到Derek的責罵的.

Input

第一行一個整數T,表示有T組數據。
每組數據第一行一個正整數N(N<=50000),表示敵人有N個工兵營地,接下來有N個正整數,第i個正整數ai代表第i個工兵營地裏開始時有ai個人(1<=ai<=50)。
接下來每行有一條命令,命令有4種形式:

(1) Add i j,i和j為正整數,表示第i個營地增加j個人(j不超過30)
(2)Sub i j ,i和j為正整數,表示第i個營地減少j個人(j不超過30);
(3)Query i j ,i和j為正整數,i<=j,表示詢問第i到第j個營地的總人數;
(4)End 表示結束,這條命令在每組數據最後出現;
每組數據最多有40000條命令

Output

對第i組數據,首先輸出“Case i:”和回車,
對於每個Query詢問,輸出一個整數並回車,表示詢問的段中的總人數,這個數保持在int以內。

Sample Input

1
10
1 2 3 4 5 6 7 8 9 10
Query 1 3
Add 3 6
Query 2 7
Sub 10 2
Add 6 3
Query 3 10
End 

Sample Out

Case 1:
6
33
59

由題意可知,這是一個單點修改與區間詢問的問題

個人習慣

#define lson th<<1
#define rson th<<1|1

由於綜上線段樹的性質(2),可以用這種方式來表示他們左右子節點。

分步解構

遞歸建樹:

void pushup(int th)
{
    sum[th]=sum[lson]+sum[rson];//簡單的結合律
}

void build(int l,int r,int th)
{
    if(l==r)//當為葉子節點時
    {
        tree[th]=a[l];
        return;
    }
    int mid=(l+r)>>1;
    build(l,mid,lson);//構建左節點
    build(mid+1,r,rson);//構建右節點
    pushup(th);//從兩個子節點更新
}

單點修改,運用二分思想

void update(int l,int r/*當前區間的左右端點*/,
int pos/*所要修改的點*/,
int th/*子樹的編號*/,
int k/*所要修改的值*/)
{
    if(l==pos && r==pos)//當找到這個節點時修改
    {
        sum[th]+=k;
        return;
    }
    int mid=(l+r)>>1;//一分為二
    if(pos<=mid) update(l,mid,pos,lson,k);//當pos在右區間時,遞歸右子樹
    else update(mid+1,r,pos,rson,k);//當pos在左區間時,遞歸左子樹
    pushup(th);//動過了當然要自下而上更新啦
}

時間復雜度網上大有分析,這裏便不贅述

區間詢問

int query(int l,int r,int x,int y,int th)
{
    if(x<=l&&r<=y){
        return sum[th];//當被詢問區間完全包含當前區間時,直接返回
    }
    int res=0;
    int mid=(l+r)>>1;
    if(x<=mid)  res+=query(l,mid,x,y,lson);
    if(mid<y) res+=query(mid+1,r,x,y,rson);
    return res; 
}

標程

#include<cstdio>
#include<iostream>
#include<cstring>
#define maxn 50005
#define lson th<<1
#define rson th<<1|1
int sum[maxn*4];
int a[maxn];
void pushup(int th)
{
    sum[th]=sum[lson]+sum[rson];
}
void build(int l,int r,int th)
{
    if(l==r)
    {
        sum[th]=a[l];
        return;
    }
    int mid=(l+r)>>1;
    build(l,mid,lson);
    build(mid+1,r,rson);
    pushup(th);
}
void update(int l,int r,int pos,int th,int k)
{
    if(l==pos && r==pos)
    {
        sum[th]+=k;
        return;
    }
    int mid=(l+r)>>1;
    if(pos<=mid) update(l,mid,pos,lson,k);
    else update(mid+1,r,pos,rson,k);
    pushup(th);
}
int query(int l,int r,int x,int y,int th)
{
    if(x<=l&&r<=y){
        return sum[th];
    }
    int res=0;
    int mid=(l+r)>>1;
    if(x<=mid)  res+=query(l,mid,x,y,lson);
    if(mid<y) res+=query(mid+1,r,x,y,rson);
    return res; 
}
int main()
{
    int T;
    scanf("%d",&T);
    for(int i=1;i<=T;i++)
    {
        printf("Case %d:\n",i);
        int n;
        scanf("%d",&n);
        for(int i=1;i<=n;i++) scanf("%d",a+i);
        build(1,n,1);
        char str[7];
        while(scanf("%s",str)){
            if(str[0]=='E') break;
            if(str[0]=='A')
            {  
                int pos,k;
                scanf("%d%d",&pos,&k);
                update(1,n,pos,1,k);
            }
            else if(str[0]=='S')
            {
                int pos,k;
                scanf("%d%d",&pos,&k);
                update(1,n,pos,1,-1*k);
            }
            else{
                int x,y;
                scanf("%d%d",&x,&y);
                printf("%d\n",query(1,n,x,y,1));
            }
        }
    }
}

B-I Hate It HDU-1754

Time Limit: 9000/3000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 107180 Accepted Submission(s): 40245

Problem Description

很多學校流行一種比較的習慣。老師們很喜歡詢問,從某某到某某當中,分數最高的是多少。
這讓很多學生很反感。

不管你喜不喜歡,現在需要你做的是,就是按照老師的要求,寫一個程序,模擬老師的詢問。當然,老師有時候需要更新某位同學的成績。

Input

本題目包含多組測試,請處理到文件結束。
在每個測試的第一行,有兩個正整數 N 和 M ( 0<N<=200000,0<M<5000 ),分別代表學生的數目和操作的數目。
學生ID編號分別從1編到N。
第二行包含N個整數,代表這N個學生的初始成績,其中第i個數代表ID為i的學生的成績。
接下來有M行。每一行有一個字符 C (只取‘Q‘或‘U‘) ,和兩個正整數A,B。
當C為‘Q‘的時候,表示這是一條詢問操作,它詢問ID從A到B(包括A,B)的學生當中,成績最高的是多少。
當C為‘U‘的時候,表示這是一條更新操作,要求把ID為A的學生的成績更改為B。

Output

對於每一次詢問操作,在一行裏面輸出最高成績。

Sample Input

5 6
1 2 3 4 5
Q 1 5
U 3 6
Q 3 4
Q 4 5
U 2 9
Q 1 5

Sample Output

5
6
5
9
HintHuge input,the C function scanf() will work better than cin

由題意可知,此題為區間最大值與單點修改,較上面A題,編碼難度較為簡單

這裏單點修改與建樹就不再贅述,因為這裏只要修改pushup()函數

void pushup(int th)
{
    maxx[th]=max(maxx[lson],maxx[rson]);
}

簡單的詢問

int query(int l,int r,int x,int y,int th)
{
    if(x>r || y<l) return 0; //完全沒有交集的情況
    if(x<=l && r<=y) return maxx[th]; //詢問區間包含當前區間
    int mid=(l+r) >> 1;
    return max(query(l,mid,x,y,lson),query(mid+1,r,x,y,rson)); //遞歸詢問
}

由這道題,大家應該能舉一反三,寫出區間最小值

標程

#include<cstdio>
#include<iostream>
#define maxn 200005
#define lson th<<1
#define rson th<<1|1
using namespace std;
int maxx[maxn*4];
int a[maxn];

void pushup(int th)
{
    maxx[th]=max(maxx[lson],maxx[rson]);
}
void build(int l,int r,int th)
{
    if(l==r)
    {
        maxx[th]=a[l];
        return;
    }
    int mid=(l+r) >> 1;
    build(l,mid,lson);
    build(mid+1,r,rson);
    pushup(th);
}

void update(int l,int r,int pos, int th ,int k)
{
    if(l==pos&&r==pos)
    {
        maxx[th]=k;
        return;
    }
    int mid=(l+r) >> 1;
    if(pos<=mid) update(l,mid,pos,lson,k);
    else update(mid+1,r,pos,rson,k);
    pushup(th);
}



int query(int l,int r,int x,int y,int th)
{
    if(x>r || y<l) return 0;
    if(x<=l && r<=y) return maxx[th];
    int mid=(l+r) >> 1;
    return max(query(l,mid,x,y,lson),query(mid+1,r,x,y,rson));
}

int n,m;

int main()
{
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        for(int i=1;i<=n;i++)
        {  
        scanf("%d",&a[i]);
        }
    build(1,n,1);
    for(int i=1;i<=m;i++)
    {
        char str[5];
        scanf("%s",str);
        if(str[0]=='Q') 
        {
            int x,y;
            scanf("%d%d",&x,&y);
            printf("%d\n",query(1,n,x,y,1));
        }
        else{
            int pos,k;
            scanf("%d%d",&pos,&k);
            update(1,n,pos,1,k);
        }
    }
    }
    return 0;
}

C-A Simple Problem with Integers

Time Limit: 5000MS Memory Limit: 131072K
Total Submissions: 147182 Accepted: 45731
Case Time Limit: 2000MS
Description

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.

Input

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.

Output

You need to answer all Q commands in order. One answer in a line.

Sample Input

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

Sample Output

4
55
9
15
Hint

The sums may exceed the range of 32-bit integers.
Source

POJ Monthly--2007.11.25, Yang Yi

我為大家大致翻譯一下題幹,意為需維護一個長度為N的int數組A,相對應的Q組詢問,有兩種操作,C l r k 意為將[l,r]區間每個數加上k,Q l r,詢問[l,r]的區間和。

這便是區間修改問題。

這裏要使用一種標記數組,lazytag

lazytag存儲在父節點上,為該區間總共需要加上的數值,所以為了維護線段樹,每次向下層遞歸前,都要下傳lazytag

我們先來看一下區間修改函數

inline void update(ll l,/*當前區間左端點*/
ll r,/*當前區間右端點*/
ll x,/*所詢問區間左端點*/
ll y,/*所詢問區間右端點*/
ll th,
ll k)
{
    if(x<=l && r<=y)//若完全包含,便把值記錄在tag上
    {
        add(l,r,th,k);
        return;
    }
    pushdown(l,r,th);//遞歸前下傳標記
    ll mid=l+r >>1;
    if(x<=mid) update(l,mid,x,y,lson,k);
    if(y>mid) update(mid+1,r,x,y,rson,k);
    pushup(th);
}

add函數

inline void add(ll l,ll r,ll th,ll k)
{
    tag[th]+=k;//tag更新
    sum[th]+=(r-l+1)*k;//自身維護
    return;
}

這裏有個pushdown函數,是pushup函數的反向操作

inline void pushdown(ll l,ll r,ll th)
{
    if(tag[th]==0) return;//若tag為0則返回
    ll mid=l+r >>1;
    add(l,mid,lson,tag[th]);//下傳標記
    add(mid+1,r,rson,tag[th]);
    tag[th]=0;//別忘了清零
}

這裏為什麽只下傳一層呢:因為正如之前所說,每次向下遞歸前都要下傳標記,所以到下層標記自然會下傳

所以這裏的詢問有了些小小的變化

ll query(ll l,ll r,ll x,ll y,ll th)
{
    if(x<=l && r<=y)
    {
        return sum[th];
    }
    ll mid=l+r >>1;
    ll res=0;
    pushdown(l,r,th);
    if(x<=mid) res+=query(l,mid,x,y,lson);
    if(y>mid) res+=query(mid+1,r,x,y,rson);
    return res;
}

然而為什麽要用longlong呢,題中明明說是int數組

這時我們再通讀題面,會發現一行話

The sums may exceed the range of 32-bit integers.

區間和可能會超過32位int類型

這便是要用longlong的原因

標程

#include<cstdio>
#include<cstring>
#include<iostream>
#define lson th<<1
#define rson th<<1|1
#define maxn 100005
typedef long long ll;
using namespace std;
ll sum[maxn*4];
ll a[maxn];
ll n,m;
ll tag[maxn*4];
inline void add(ll l,ll r,ll th,ll k)
{
    tag[th]+=k;
    sum[th]+=(r-l+1)*k;
    return;
}
inline void pushup(ll th)
{
    sum[th]=sum[lson]+sum[rson];
    return;
}
inline void pushdown(ll l,ll r,ll th)
{
    if(tag[th]==0) return;
    ll mid=l+r >>1;
    add(l,mid,lson,tag[th]);
    add(mid+1,r,rson,tag[th]);
    tag[th]=0;
}
inline void build(ll l,ll r,ll th)
{
    if(l==r)
    {
        sum[th]=a[l];
        return;
    }
    ll mid=l+r >> 1;
    build(l,mid,lson);
    build(mid+1,r,rson);
    pushup(th);
}
inline void update(ll l,ll r,ll x,ll y,ll th,ll k)
{
    if(x<=l && r<=y)
    {
        add(l,r,th,k);
        return;
    }
    pushdown(l,r,th);
    ll mid=l+r >>1;
    if(x<=mid) update(l,mid,x,y,lson,k);
    if(y>mid) update(mid+1,r,x,y,rson,k);
    pushup(th);
}
ll query(ll l,ll r,ll x,ll y,ll th)
{
    if(x<=l && r<=y)
    {
        return sum[th];
    }
    ll mid=l+r >>1;
    ll res=0;
    pushdown(l,r,th);
    if(x<=mid) res+=query(l,mid,x,y,lson);
    if(y>mid) res+=query(mid+1,r,x,y,rson);
    return res;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%lld",&a[i]);
    }
    build(1,n,1);
    while(m--)
    {
        char str[5];
        scanf("%s",str);
        if(str[0]=='C')
        {
            ll x,y,k;
            scanf("%lld%lld%lld",&x,&y,&k);
            update(1,n,x,y,1,k);
        }
        else{
            ll x,y;
            scanf("%lld %lld",&x,&y);
            printf("%lld\n",query(1,n,x,y,1));
        }
    }
}

理解了之後代碼難度並不是非常大,畢竟只是最最基礎的應用而已。

數據結構的半夜——線段樹學習筆記1