1. 程式人生 > >線段樹初步(1)

線段樹初步(1)

建立 命令 ase img rom div roman 字符串 當我

蒟蒻終於要開始好好學線段樹了……

線段樹是一種二叉樹形結構(二叉搜索樹),屬於平衡樹的一種。它將線段區間組織成樹形的結構,並用每個節點來表示一條線段[a,b]。每個節點的左右兒子線段分別是該線段的左半[a,(a+b)/2]和右半[(a+b)/2+1,b]區間,遞歸定義之後就是一棵線段樹。

1.建樹

既然線段樹是遞歸建立的,那麽我們就在遞歸到葉節點的時候把數據植入,之後遞歸返回的時候,將要修改的節點的值修改為其子節點的值之和即可。

void build(int l,int r,int x)//遞歸建樹,到葉節點輸入,之後遞歸返回修改 
{
    if(l == r)    
    {
        tree[x] 
= read(); return; } int mid = (l+r) >> 1; build(l,mid,x << 1); build(mid+1,r,x << 1 | 1); tree[x] = tree[x << 1] + tree[x << 1 | 1]; }

2.單點修改(增加或者刪除)

因為每條節點表示一條線段……所以在單點修改的時候我們只要找到需要修改的節點所在的位置,之後當當前區間l==r的時候修改這個點就可以。如果要刪除,就往裏面傳負數就行。

註意修改結束之後返回的時候也要……遞歸把其父親和祖先的值都修改一遍。

void update(int k,int val,int l,int r,int x)//單點修改 
{
    if(l == r)
    {
        tree[x] += val;
        return;
    }
    int mid = (l+r) >> 1;
    if(k <= mid) update(k,val,l,mid,x << 1);
    else update(k,val,mid+1,r,x << 1 | 1);
    tree[x] = tree[x << 1] + tree[x << 1
| 1]; }//k為要修改的節點編號,val為修改值,l,r為左右區間

3.區間查詢

給定一段區間,求區間中所有元素之和。

當我們當前訪問的區間其被完全包含於所求區間的時候,直接把這段區間和加上即可。

否則的話我們進行二分,把所求區間分割成更小的區間,之後找到能被其完全包含的區間進行累加即可。(也就是相當於我們把所求區間分割成了連續的小區間之後求和)

累加的過程同樣是在遞歸中完成的。

int query(int kl,int kr,int l,int r,int x)
{
    if(kl <= l && kr >= r) return tree[x];
    int mid = (l+r) >> 1;
    int sum = 0;
    if(kl <= mid) sum += query(kl,kr,l,mid,x << 1);
    if(kr > mid) sum += query(kl,kr,mid+1,r,x << 1 | 1);
    return sum;    
} //kl為查詢左端點,kr為查詢右端點,l,r為當前區間作用端點,x為節點編號 

會了這幾個操作之後就可以做一道稍微簡單一點的練習題了。

C國的死對頭A國這段時間正在進行軍事演習,所以C國間諜頭子Derek和他手下Tidy又開始忙乎了。A國在海岸線沿直線布置了N個工兵營地,DerekTidy的任務就是要監視這些工兵營地的活動情況。由於采取了某種先進的監測手段,所以每個工兵營地的人數C國都掌握的一清二楚,每個工兵營地的人數都有可能發生變動,可能增加或減少若幹人手,但這些都逃不過C國的監視。
中央情報局要研究敵人究竟演習什麽戰術,所以Tidy要隨時向Derek匯報某一段連續的工兵營地一共有多少人

【輸入格式】

第一行一個整數T,表示有T組數據。
每組數據第一行一個正整數NN<=50000,表示敵人有N個工兵營地,接下來有N個正整數,i個正整數ai代表第i個工兵營地裏開始時有ai個人(1<=ai<=50)。
接下來每行有一條命令,命令有4種形式:
(1) Add i j,ij為正整數,表示第i個營地增加j個人(j不超過30
(2)Sub i j ,ij為正整數,表示第i個營地減少j個人(j不超過30;
(3)Query i j ,ij為正整數,i<=j,表示詢問第i到第j個營地的總人數;
(4)End 表示結束,這條命令在每組數據最後出現;
每組數據最多有40000條命令

【輸出格式】

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

技術分享圖片這道題為了方便我把字符串都改成了數字。

操作就三個,建樹,單點修改,區間查詢,上面三個操作正好夠用。

#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<iostream>
#include<cstdlib>
#include<queue>
#define rep(i,a,n) for(int i = a;i <= n;i++)
#define per(i,n,a) for(int i = n;i >= a;i--)
#define enter putchar(‘\n‘)

using namespace std;
const int M = 1000005;
typedef long long ll;

int tree[M*4],k = 1,c,t,n,p,q,num;
int read()
{
    int ans = 0,op = 1;
    char ch = getchar();
    while(ch < 0 || ch > 9)
    {
        if(ch == -) op = -1;
        ch = getchar();
    }
    while(ch >= 0 && ch <= 9)
    {
        ans *= 10;
        ans += ch - 0;
        ch = getchar();
    }
    return ans * op;
}
void build(int l,int r,int x)//遞歸建樹,到葉節點輸入,之後遞歸返回修改 
{
    if(l == r)    
    {
        tree[x] = read();
        return;
    }
    int mid = (l+r) >> 1;
    build(l,mid,x << 1);
    build(mid+1,r,x << 1 | 1);
    tree[x] = tree[x << 1] + tree[x << 1 | 1];
} 
void update(int k,int val,int l,int r,int x)//單點修改 
{
    if(l == r)
    {
        tree[x] += val;
        return;
    }
    int mid = (l+r) >> 1;
    if(k <= mid) update(k,val,l,mid,x << 1);
    else update(k,val,mid+1,r,x << 1 | 1);
    tree[x] = tree[x << 1] + tree[x << 1 | 1];
}//k為要修改的節點編號,val為修改值,l,r為左右區間
int query(int kl,int kr,int l,int r,int x)
{
    if(kl <= l && kr >= r) return tree[x];
    int mid = (l+r) >> 1;
    int sum = 0;
    if(kl <= mid) sum += query(kl,kr,l,mid,x << 1);
    if(kr > mid) sum += query(kl,kr,mid+1,r,x << 1 | 1);
    return sum;    
} //kl為查詢左端點,kr為查詢右端點,l,r為當前區間作用端點,x為節點編號 
int main()
{
    t = read();
    while(t--)
    {
        n = read();
        build(1,n,1);
        c = read();
        rep(i,1,c)
        {
            num = read();
            if(num == 4) break;
            p = read(),q = read();
            if(num == 1) update(p,q,1,n,1);        
            if(num == 2) update(p,-q,1,n,1);
            if(num == 3) printf("%d\n",query(p,q,1,n,1));
        }
    }
    return 0;
}
/*
1
10
1 2 3 4 5 6 7 8 9 10
7
3 1 3
1 3 6
3 2 7
2 10 2
1 6 3
3 3 10
4
*/

線段樹初步(1)