線段樹初步(1)
蒟蒻終於要開始好好學線段樹了……
線段樹是一種二叉樹形結構(二叉搜索樹),屬於平衡樹的一種。它將線段區間組織成樹形的結構,並用每個節點來表示一條線段[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個工兵營地,Derek和Tidy的任務就是要監視這些工兵營地的活動情況。由於采取了某種先進的監測手段,所以每個工兵營地的人數C國都掌握的一清二楚,每個工兵營地的人數都有可能發生變動,可能增加或減少若幹人手,但這些都逃不過C國的監視。
中央情報局要研究敵人究竟演習什麽戰術,所以Tidy要隨時向Derek匯報某一段連續的工兵營地一共有多少人。
【輸入格式】
第一行一個整數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條命令
【輸出格式】
對第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)