線段樹


數據結構--線段樹

1、定義

線段樹是一種二叉搜索樹,與區間樹類似,它將一個區間劃分紅一些單元區間,每一個單元區間對應線段樹中的一個葉結點。對於線段樹中的每個非葉子節點$[a,b]$,它的左兒子表示的區間爲$[a,(a+b)/2]$,右兒子表示的區間爲$[(a+b)/2+1,b]$。所以線段樹是平衡二叉樹,最後的葉子節點數目爲$N$,即整個線段區間的長度。使用線段樹能夠快速的查找某一個節點在若干條線段中出現的次數,時間複雜度爲$O(logN)$。而未優化的空間複雜度爲$4N$node

大家可能會問什麼是區間樹,我也不知道。

$如上圖就是一顆[1,8]的線段樹。$ios

2、性質

$每一個節點的左孩子區間範圍爲[l,mid],右孩子爲[mid+1,r]。$
$對於節點k,左孩子爲k * 2,右孩子爲k * 2 + 1,和二叉樹同樣$數組

3、基本操做

$線段樹的基礎操做主要有5個:建樹、單點查詢、單點修改、區間查詢、區間修改。$數據結構

0.結構體

可使用結構體也可使用數組,看我的喜愛。優化

struct node{
    int l,r,w;//l,r分別表示區間左右端點,w表示區間和
}tree[MAXN*4+1];//注意線段樹要開四倍空間。

1.建樹

void build(int l,int r,int now){
    tree[now].l=l,tree[now].r=r;//記下now這個節點所表示的區間。
    if(l==r){//now節點爲葉子結點。
        scanf("%d",tree[now].w);//讀入葉子結點的值。
        return;//不用進行下面的了。
    }
    int mid=(l+r)>>1;//>>1至關於/2
    build(l,mid,now<<1);//<<1至關於*2
    build(mid+1,r,now<<1|1);//<<1|1至關於*2+1
    tree[now].w=tree[now<<1].w+tree[now<<1|1].w;//更新區間和
}

2.單點查詢

詢問第$x$個點的值。ui

void ask_single(int x,int now){
    if(tree[now].l==tree[now].r){//葉子結點,即最終答案。
        ans=tree[now].w;
        return;
    }
    int mid=(tree[now].l+tree[now].r)>>1;//計算區間的中點。
    if(x<=mid){
        ask_single(x,now<<1);//查找該點的左孩子
    }else{
        ask_single(x,now<<1|1);//查找該點的有孩子
    }
}

3.單點修改

給第$x$個點加上$y$。(和單點查詢差很少qwq)spa

void updata_single(int x,int y,int now){
    if(tree[now].l==tree[now].r){
        tree[now].w+=y;
        return;
    }
    int mid=(tree[now].l+tree[now].r)>>1;
    if(x<=mid){
        updata_single(x,y,now<<1);
    }else{
        updata_single(x,y,now<<1|1);
    }
    tree[now].w=tree[now<<1].w+tree[now<<1|1].w;//單點修改後要更新區間和
}


修改的過程像上圖同樣遞歸修改,當修改完單點後再去更新上面的區間和。code

4.區間修改

建議先看下面的區間查詢。
將$[x,y]$區間每個數加上$x$。
區間裏都是點,進行$y-x+1$次單點修改。(太慢了,應該不會有人用吧。)htm

void update_range(int x,int y,int c){
    for(int i=x;i<=y;++i){
        update_single(i,c,1);
    }
}

上面這個作法數據不水的話就$TLE$了(在線段樹1中只能拿$70$分)。
將某區間每個數加上$x$,這個是能夠加優化的。
修改須要辣麼多時間,不修改不就能夠了嗎?
差很少,是在沒用到的時候不修改。
就像是過年各類親戚給你壓歲錢,而後都到了你家長手裏,$\color{red}{你用的時候再給你}$。(而後這錢可能永遠也到不了你手裏了,qwq)。
實現方法:
1.用一個標記記錄下這個增量。
2.當要修改的區間徹底包含當前區間就給當前區間的標記加上這個增量,再也不向下遞歸。
當須要查詢子節點時怎麼辦?
用到一個下放操做。
1.當前節點的標記累加到子節點的標記中。
2.修改子節點狀態。
3.該節點標記清$0$。
結構體:

struct node{
    int w,l,r,lazy;//lazy就是這個標記。
}tree[MAXN*4+1];

下放操做:

inline void pushdown(int now){
    if(tree[now].lazy){
        tree[now<<1].lazy+=tree[now].lazy;
        tree[now<<1|1].lazy+=tree[now].lazy;//將lazy標記向下傳.
        tree[now<<1].w+=(tree[now<<1].r-tree[now<<1].l+1)*tree[now].lazy;
        tree[now<<1|1].w+=(tree[now<<1|1].r-tree[now<<1|1].l+1)*tree[now].lazy;//更新區間和
        tree[now].lazy=0;//清零
    }
}

區間修改:

void update_range(int x,int y,int c,int now){
    if(tree[now].l>=x&&tree[now].r<=y){//當前區間包含於要修改的區間.
        tree[now].w+=(tree[now].r-tree[now].l+1)*c;
        tree[now].lazy+=c;
        return;
    }
    if(tree[now].lazy) pushdown(now);//下傳
    int mid=(tree[now].l+tree[now].r)>>1;
    if(x<=mid) update_range(x,y,c,now<<1);
    if(y>mid) update_range(x,y,c,now<<1|1);
    tree[now].w=tree[now<<1].w+tree[now<<1|1].w;//這幾行和區間查詢差很少
}

由於加入了lazy標記因此其餘的操做也有改變。
單點查詢:

int ask_single(int x,int now){
    if(tree[now].l==tree[now].r){
        return tree[now].w;
    }
    if(tree[now].lazy) pushdown(now);//要改的地方只有一個下傳 
    int mid=(tree[now].l+tree[now].r)>>1;
    if(x<=mid) ask_single(x,now<<1);
    else ask_single(x,now<<1|1);
}

區間查詢:

void ask_range(int x,int y,int now){
    if(tree[now].l>=x&&tree[now].r<=y){
        ans+=tree[now].w;
        return;
    }
    if(tree[now].lazy) pushdown(now);//要改的地方只有一個下傳 
    int mid=(tree[now].l+tree[now].r)>>1;
    if(x<=mid) ask_range(x,y,now<<1);
    if(y>mid) ask_range(x,y,now<<1|1);
}

單點修改

void update_single(int x,int c,int now){//
    if(tree[now].l==tree[now].r){
        tree[now].w+=c;
        return;
    }
    if(tree[now].lazy) pushdown(now);//要改的地方只有一個下傳 
    int mid=(tree[now].l+tree[now].r)>>1;
    if(x<=mid) update_single(x,c,now<<1);
    else update_single(x,c,now<<1|1);
    tree[now].w=tree[now<<1].w+tree[now<<1|1].w;
}

5.區間查詢

求出$[x,y]$區間每個數的和。
在查詢的過程當中存在如下幾種狀況:

void ask_range(int x,int y,int now){
    if(tree[now].l>=x&&tree[now].r<=y){//[l,r]是[x,y]的子集
        ans+=tree[now].w;
        return;//不結束的話進行下面的會重複計算。
    }
    int mid=(tree[now].l+tree[now].r)>>1;
    if(x<=mid){//要查詢的區間在當前區間的左邊
        ask_range(x,y,now<<1);
    }
    if(y>mid){//要查詢的區間在當前區間的右邊
        ask_range(x,y,now<<1|1);
    }
}

6.總

#include<iostream>
#include<cstring>
#include<string>
#include<cstdio>
#include<algorithm>
#define MAXN 500001
using namespace std;
struct node{
    int l,r,w;
    int lazy;
}tree[MAXN<<2];
int ans;
inline int read(){
    int x=0;bool f=0;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')f=!f;c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
    return f?-x:x;
}
void build(int l,int r,int now){
    tree[now].l=l,tree[now].r=r;
    tree[now].lazy=0;
    if(tree[now].l==tree[now].r){
        tree[now].w=read();
        return;
    }
    int mid=(tree[now].l+tree[now].r)>>1;
    build(l,mid,now<<1),build(mid+1,r,now<<1|1);
    tree[now].w=tree[now<<1].w+tree[now<<1|1].w;
}
inline void pushdown(int now){
    tree[now<<1].lazy+=tree[now].lazy;
    tree[now<<1|1].lazy+=tree[now].lazy;
    tree[now<<1].w+=tree[now].lazy*(tree[now<<1].r-tree[now<<1].l+1);
    tree[now<<1|1].w+=tree[now].lazy*(tree[now<<1|1].r-tree[now<<1|1].l+1);
    tree[now].lazy=0;
}
void update_single(int x,int k,int now){//單點修改
    if(tree[now].l==tree[now].r){
        //tree[now].w=k;將x這個位置的數改成k
        //tree[now].w+=k;將x這個位置的數加上k
        return;
    }
    if(tree[now].lazy!=0) pushdown(now);
    int mid=(tree[now].l+tree[now].r)>>1;
    if(x<=mid) update_single(x,k,now<<1);
    else update_single(x,k,now<<1|1);
    tree[now].w=tree[now<<1].w+tree[now<<1|1].w;
}
int ask_single(int x,int now){//單點查詢
    if(tree[now].l==tree[now].r) return tree[now].w;
    if(tree[now].lazy!=0) pushdown(now);
    int mid=(tree[now].l+tree[now].r)>>1;
    if(x<=mid) return ask_single(x,now<<1);
    else return ask_single(x,now<<1|1);
}
void update_range(int x,int y,int k,int now){//區間修改
    if(tree[now].l>=x&&tree[now].r<=y){
        tree[now].w+=(tree[now].r-tree[now].l+1)*k;
        tree[now].lazy+=k;
        return;
    }
    if(tree[now].lazy!=0) pushdown(now);
    int mid=(tree[now].l+tree[now].r)>>1;
    if(x<=mid) update_range(x,y,k,now<<1);
    if(y>mid) update_range(x,y,k,now<<1|1);
    tree[now].w=tree[now<<1].w+tree[now<<1|1].w;
}
void ask_range(int x,int y,int now){
    if(tree[now].l>=x&&tree[now].r<=y){
        ans+=tree[now].w;
        return;
    }
    if(tree[now].lazy!=0) pushdown(now);
    int mid=(tree[now].l+tree[now].r)>>1;
    if(x<=mid) ask_range(x,y,now<<1);
    if(y>mid) ask_range(x,y,now<<1|1);
}

int main(){
    return 0;
}

4、題目

單點修改、區間查詢模板

Codevs 1080
洛谷P3374

區間修改、區間查詢模板

洛谷P3372
Codevs1082

區間修改、單點查詢

洛谷P3368
Codevs1081

5、鳴謝

學姐的Blog

百度百科

相關文章
相關標籤/搜索