1. 程式人生 > >線段樹(區間樹)

線段樹(區間樹)

  • 線段樹

  1. 定義及作用

  2. 建樹

  3. 基本操作

 一.定義及作用

線段樹是一種二叉搜尋樹,它將一個區間劃分成一些單元區間,每個單元區間對應線段樹中的一個葉結點。使用線段樹可以快速的查詢某一個節點在若干條線段中出現的次數,時間複雜度為O(logN)。而未優化的空間複雜度為2N。

二.建樹

1.先定義一個結構體以便每個節點能存東西

struct node
{
    int l;//區間左端點
    int r;//區間右端點
    int w;//區間儲存的值
}tree[400001];//開四倍大小

2.遞迴建樹

void build(int k,int l,int r)//建樹 
{
    tree[k].l=l,tree[k].r=r;
    if(tree[k].l==tree[k].r)                   //遞到葉子節點的時候歸
    {
        scanf("%d",&tree[k].w);                //輸入各葉子節點的值
        return;                                //出遞迴
    }
    int m=(l+r)/2;                             //準備生成左右子樹
    build(k*2,l,m);                            //遞迴左子樹
    build(k*2+1,m+1,r);                        //左子樹遞迴完遞迴右子樹
    tree[k].w=tree[k*2].w+tree[k*2+1].w;       //向上更新各區間值就是pushup(k)
}

三、線段樹的基本操作

向上更新

void pushup(int k)
{
    tree[k].w=tree[k*2].w+tree[k*2+1].w;    //父親的值根據其子孫的值確定
}

向下更新

void pushdown(int k)
{
    tree[k*2].f+=tree[k].f;                                      //將更改的單位值傳遞給左兒子
    tree[k*2+1].f+=tree[k].f;                                    //將更改的單位值傳遞給右兒子
    tree[k*2].w+=tree[k].f*(tree[k*2].r-tree[k*2].l+1);          //更新左兒子儲存的值
    tree[k*2+1].w+=tree[k].f*(tree[k*2+1].r-tree[k*2+1].l+1);    //更新右兒子儲存的值
    tree[k].f=0;                                                 //向下更新完畢取消標記值
}

1.單點查詢

void ask_point(int k,int x)
{
    if(tree[k].l==tree[k].r)                 //當查到葉子節點時
    {    
        ans=tree[k].w;                       //ans為全域性變數傳遞查詢的值
        return ;                             //出遞迴   
    }
    if(tree[k].f) pushdown(k);               //向下更新
    int m=(tree[k].l+tree[k].r)/2;           //取區間中值,準備遞迴左右子樹查詢
    if(x<=m) ask_point(k*2);                 //若查詢的單點為區間中值或在區間左邊則遞迴左子樹
    else ask_point(k*2+1);                   //否則遞迴右子樹
}

2.單點更新

void change_point(int k,int x,int v)
{
    if(tree[k].l==tree[k].r)
    {
        tree[k].w+=v;
        return;
    }
    if(tree[k].f) pushdown(k);
    int m=(tree[k].l+tree[k].r)/2;
    if(x<=m) change_point(k*2);
    else change_point(k*2+1);
    tree[k].w=tree[k*2].w+tree[k*2+1].w;
}

3.區間查詢

void ask_interval(int k,int a,int b)
{
    if(tree[k].l>=a&&tree[k].r<=b)                 //區間在a,b之間
    {
        ans+=tree[k].w;
        return;
    }
    if(tree[k].f) pushdown(k);
    int m=(tree[k].l+tree[k].r)/2;
    if(a<=m) ask_interval(k*2);              //若本區間中值等於a或在a右邊(說明左邊還能遞迴)
    if(b>m) ask_interval(k*2+1);             //若本區間中值在b左邊(說明右邊還能遞迴)    
}

4.區間更新(未解釋懶標記)

void change_interval(int k)
{
    if(tree[k].l>=a&&tree[k].r<=b)
    {
        tree[k].w+=(tree[k].r-tree[k].l+1)*y;
        tree[k].f+=y;
        return;
    }
    if(tree[k].f) pushdown(k);
    int m=(tree[k].l+tree[k].r)/2;
    if(a<=m) change_interval(k*2);
    if(b>m) change_interval(k*2+1);
    tree[k].w=tree[k*2].w+tree[k*2+1].w;
}