咱們在這篇博客裏將具體介紹一種超級毒瘤超級高效的算法
線段樹html
首先來認識一下線段樹
什麼是線段樹呢:
線段樹是一種二叉樹,也就是對於一個線段,咱們會用一個二叉樹來表示。好比說一個長度爲6的線段,咱們能夠表示成這樣
這個圖是什麼意思呢?
node
tree[rt].sum = tree[l].sum + tree[r].sum
struct node{ int l,r,sum;//l表示左兒子 r表示右兒子 sum表示當前節點存儲的權值 }tree[maxn*4]; void build(int i,int l,int r){ tree[i].l = l;tree[i].r = r; if(l == r){ tree[i].sum = a[l];//a數組存儲給出的數組初始值 return; } int mid = (l+r)/2; build(i*2,l,mid); build(i*2+1,mid+1,r); tree[i].sum = tree[i*2].sum+tree[i*2+1].sum; return; }
這就是線段樹的建樹方法 若是你要問爲何咱們要花好幾倍的內存去建樹來完成一個數組就能完成的事情 那就是由於咱們須要讓這個超級大的數組去幹一些比較困難的事情
那什麼是比較困難的事情呢 讓咱們進入下個專題算法
for(int i = 1;i<=5;++i){ans+=a[i]};
所以用代碼怎麼實現呢 也很簡單
先讓咱們總結一下線段樹的查詢方式:數組
int search(int rt,int l,int r){ if(tree[rt].r < l ||tree[rt].l > r)return 0; if(tree[rt].l >= l && tree[rt].r <= r)return tree[rt].sum; int ans = 0; if(tree[rt*2].r >= l)ans += search(2*rt,l,r); if(tree[rt*2+1].l <= r)ans += search(2*rt+1,l,r); return ans; }
區間修改和單點查詢的方法有不少
爲了一會對pushdown的講解 咱們這裏說一種比較便於下面理解的方法
區間修改和區間查詢很像函數
void build(int l,int r,int rt){ tree[rt].num=0; tree[rt].l=l; tree[rt].r=r; if(l==r) return ; int mid=(r+l)/2; build(l,mid,rt*2); build(mid+1,r,rt*2+1); } void add(int rt,int l,int r,int k){ if(tree[rt].l>=l && tree[rt].r<=r){ tree[rt].num+=k; return ; } if(tree[rt*2].r>=l) add(rt*2,l,r,k); if(tree[rt*2+1].l<=r) add(rt*2+1,l,r,k); }
前方高難
看到這樣的題你或許會想 不就是上邊那兩種放在一塊兒嗎
可是若是你真的這樣寫完了代碼你會發現 WA
爲何呢
先來回想一下剛纔的操做:將區間加上標記 最終查詢的時候去從上往下找 將標記累加最後再加上初始值
可是這樣真的能夠嗎?
答案是否認的 緣由很簡單:若是你要求1~3區間的和 而你剛剛將3~5的區間加上標記 由於1~3並不包含3~5的標記 因此咱們計算後的結果並非加k以後的和 而是初始值的和
那如何解決這個問題呢? 也很簡單:只要將咱們的標記k下放到i的兒子不就行了嗎
因此咱們的算法雛形就出來了(這也是線段樹最毒瘤並且難調最具備魅力的地方)ui
tree[rt].sum += k*(tree[rt].r - tree[rt].l + 1)
void pushdown(long long rt){ if(tree[rt].lazy != 0){//若是當前區間已經被標記 tree[rt*2].lazy += tree[rt].lazy;//下放到左兒子 tree[rt*2+1].lazy += tree[rt].lazy;//下放到右兒子 long long mid = (tree[rt].l + tree[rt].r)/2; tree[rt*2].sum += tree[rt].lazy*(mid - tree[rt*2].l + 1);//更新左兒子的值 tree[rt*2+1].sum += tree[rt].lazy*(tree[rt*2+1].r - mid);//更新右兒子的值 tree[rt].lazy = 0;//清空當前節點的懶惰標記 } return; } void add(long long rt,long long l,long long r,long long k){ if(tree[rt].l >= l && tree[rt].r <= r){//若是當前區間徹底包含在目標區間直接更新而且標記懶惰標記 tree[rt].sum += k*(tree[rt].r-tree[rt].l+1);//更新當前區間的權值 tree[rt].lazy += k;//增長懶惰標記 return; } pushdown(rt);//下放懶惰標記 if(tree[rt*2].r >= l)add(rt*2,l,r,k);//遞歸更新左兒子 if(tree[rt*2+1].l <= r)add(rt*2+1,l,r,k);//遞歸更新右兒子 tree[rt].sum = tree[rt*2].sum+tree[rt*2+1].sum;//更新當前節點的權值 return; }
區間查詢的時候和以前幾乎同樣 不一樣的是要進行懶惰標記的下放以後在累加spa
long long search(long long rt,long long l,long long r){ if(tree[rt].l >= l && tree[rt].r <= r)return tree[rt].sum;//若是當前區間徹底包含在目標區間內 直接返回當前區間的權值 if(tree[rt].r < l || tree[rt].l > r)return 0;//若是當前區間和目標區間徹底沒有關係 直接返回0 pushdown(rt);//下放懶惰標記 long long s = 0; if(tree[rt*2].r >= l)s += search(rt*2,l,r); if(tree[rt*2+1].l <= r)s += search(rt*2+1,l,r); return s;//最後返回這個區間的和 }
線段樹模型大概就是這個樣子(線段樹仍是比較受出題人青睞的難道是由於難調??)
附上練習攻略:
簡單線段樹建議用洛谷P3374【模板】樹狀數組1
洛谷P3368【模板】樹狀數組2練習板子
若是簡單線段樹沒有問題了請食用進階線段樹
通關攻略:洛谷P3372【模板】線段樹1
洛谷P3373【模板】線段樹2
洛谷P6242【模板】線段樹3code
謝謝觀看
點個關注>_<htm