1. 程式人生 > >[OI學習筆記]線段樹模板

[OI學習筆記]線段樹模板

樹狀數組 lazy 重復 優化 查詢 amp cor 葉子 scan

背景

  今天課上講了樹狀數組和線段樹,有是一臉懵逼。。。

  於是網上有惡補了一下,

  看了幾個小時blog終於懂了。。。

  由於很難解釋,就只貼兩個模板代碼(一個單點修改+單點和區間查詢,一個區間修改+單點和區間查詢)

幾點註意事項

  1.首先跑區間更新的程序,一般對於每一個點要維護一個lazy變量來臨時存儲它將要向下更新的數值,然後對於要查尋的點,直接向下更新,這樣偷懶更省時間。(具體實現過程見代碼)。

  2.空間要開到4*N(N為元素個數)(好像可以優化成2*N,但是本蒟蒻不知道怎麽優化)

  3.打線段樹坑點還是很多的,下面列舉一下我所犯過的錯誤:

    1st.睿智地把mid=(tree[k].r+tree[k].l)/2和(tree[k*2].r-tree[k*2].l+1)搞混(全國可能就我會這樣了。。。)

    2nd.下放lazy更新子節點的權時,要用父節點的lazy更新,並且區間長度要+1.

    3rd.下放完lazy父節點的lazy值要標位0!!防止重復下放!

  大概就是這些,,,下面直接上代碼

代碼

  線段樹單點修改兩種查詢:

  

#include<cstdio>
#include<iostream>
using namespace std;

const int MAXN=10010; 

int n;//n個元素,m個操作 

struct node{
    int l,r,v;
}tree[4*MAXN+1];//
其實最少開到4*MAXN就行 void build(int l,int r,int k){//當前節點存儲的線段的左右端點&這個節點的編號 tree[k].l=l;tree[k].r=r; if(l==r){ scanf("%d",&tree[k].v);//葉子節點的值可在這裏讀入! return; } int mid=(l+r)/2; build(l,mid,k*2); build(mid+1,r,k*2+1); tree[k].v=tree[k*2].v+tree[k*2
+1].v; } //單點查詢(葉子節點) int askpoint(int x,int k){//目標點在原序列中的下標 k為當前樹中的位置下標 if(tree[k].l==tree[k].r){ return tree[k].v; } int mid=(tree[k].r+tree[k].l)/2;//錯點1 // if(x<=mid)return askpoint(x,k*2); else return askpoint(x,k*2+1); } //單點修改 void changepoint(int x,int num,int k){//要在序列中的第x個數中加上num k為當前樹中的下標 if(tree[k].l==tree[k].r){ tree[k].v+=num; return; } int mid=(tree[k].r+tree[k].l)/2;//錯點1 // if(x<=mid)changepoint(x,num,k*2); else changepoint(x,num,k*2+1); tree[k].v=tree[k*2].v+tree[k*2+1].v;//重新整合區間和 } //區間查詢 int sum=0; void asksec(int tl,int tr,int l,int r,int k){ if(l>=tl&&r<=tr){ sum+=tree[k].v; return; } int mid=(tree[k].l=tree[k].r)/2; if(tl<=mid)asksec(tl,tr,l,mid,k*2); if(tr>mid)asksec(tl,tr,mid+1,r,k*2+1);//錯點2 不是else// } int main(){ cout<<"點數"<<endl;//這個程序裏不用問題數 scanf("%d",&n); cout<<"build tree"<<endl; build(1,n,1); cout<<"change point"<<endl; int x,num; scanf("%d%d",&x,&num); changepoint(x,num,1); cout<<"ask point"<<endl; scanf("%d",&x); printf("%d\n",askpoint(x,1)); cout<<"ask section"<<endl; int tl,tr; scanf("%d%d",&tl,&tr); asksec(tl,tr,1,n,1); printf("%d\n",sum); return 0; }

  線段樹區間修改兩種查詢:

  

#include<cstdio>
#include<iostream>
using namespace std;

const int MAXN=10010; 

int n;//n個元素,m個操作 

struct node{
    int l,r,v,lazy;
}tree[4*MAXN+1];//其實最少開到4*MAXN就行 

void build(int l,int r,int k){//當前節點存儲的線段的左右端點&這個節點的編號 
    tree[k].l=l;tree[k].r=r;
    if(l==r){
        scanf("%d",&tree[k].v);//葉子節點的值可在這裏讀入! 
        return;
    }
    int mid=(l+r)/2;
    build(l,mid,k*2);
    build(mid+1,r,k*2+1);
    tree[k].v=tree[k*2].v+tree[k*2+1].v;
}

//下放lazy標記函數
void putdown(int k){
    tree[k*2].lazy+=tree[k].lazy;
    tree[k*2+1].lazy+=tree[k].lazy;
    tree[k*2].v+=tree[k].lazy*(tree[k*2].r-tree[k*2].l+1);//坑點:1.要用父節點k的lazy  2.區間長度記得要+1//
    tree[k*2+1].v+=tree[k].lazy*(tree[k*2+1].r-tree[k*2+1].l+1);//坑點:同上//
    tree[k].lazy=0;//坑點:父節點的lazy要清0!!!!!// 
} 

//單點查詢(葉子節點) 
int askpoint(int x,int k){
    if(tree[k].l==tree[k].r){
        return tree[k].v;
    }
    if(tree[k].lazy)putdown(k);//防止重復下放 
    int mid=(tree[k].l+tree[k].r)/2;
    if(x<=mid)return askpoint(x,k*2);
    else return askpoint(x,k*2+1);
}

//區間修改 
void changesection(int tl,int tr,int num,int k){
    if(tl<=tree[k].l&&tr>=tree[k].r){//對查詢有用 
        tree[k].v+=num*(tree[k].r-tree[k].l+1);//坑點// 
        tree[k].lazy+=num; 
        return;
    }
    if(tree[k].lazy)putdown(k);//防止重復下放 
    int mid=(tree[k].r+tree[k].l)/2;//錯點 //
    if(tl<=mid)changesection(tl,tr,num,k*2);
    if(tr>mid)changesection(tl,tr,num,k*2+1);
    tree[k].v=tree[k*2].v+tree[k*2+1].v;
}

//區間查詢 
int sum=0;
void asksec(int tl,int tr,int l,int r,int k){
    if(l>=tl&&r<=tr){
        sum+=tree[k].v;
        return;
    }
    if(tree[k].lazy)putdown(k);//防止重復下放 
    int mid=(tree[k].r+tree[k].l)/2;
    if(tl<=mid)asksec(tl,tr,l,mid,k*2);
    if(tr>mid)asksec(tl,tr,mid+1,r,k*2+1);
}

int main(){
    freopen("in.in","r",stdin);
    cout<<"點數"<<endl;
    scanf("%d",&n);
    cout<<"build tree"<<endl; 
    build(1,n,1);
    cout<<"change section"<<endl; 
    int x,y,num;
    scanf("%d%d%d",&x,&y,&num);
    changesection(x,y,num,1);
    cout<<"ask point"<<endl;
    scanf("%d",&x);
    printf("%d\n",askpoint(x,1));
    cout<<"ask section"<<endl;
    int tl,tr;
    scanf("%d%d",&tl,&tr);
    asksec(tl,tr,1,n,1);
    printf("%d\n",sum);
    return 0;
} 

[OI學習筆記]線段樹模板