線段樹筆記(未完結系列)

emmmmm….html

昨天剛學徹底線段樹,今天先來記錄一波….c++

指不定以後還須要寫更多的東西….?git

因此說目前爲止只學習了基本操做和懶標記我也很無奈.jpg數組

線段樹的基本操做數據結構

學習線段樹的平常基礎思考:學習

給定一個長度爲n的數組,對其中某段序列進行m次以下可能操做:字體

  1. 給ai加上v
  2. 給區間[L , R]中的每個數加上v
  3. 求區間[L , R]的最大/最小值
  4. 求區間[L , R]內全部數的和
  5. 查詢ai的值

固然,這些操做均可以只用一個數組a進行模擬來完成優化

如今咱們分析一下複雜度:對於操做一、5來講,每次的時間複雜度爲O(1),由於只須要修改或輸出數組a[i]的值就行了,可是對於操做二、三、4來講,每次的時間複雜度爲O(區間長度)(時間複雜度計算我根本不會因此是搬來的現成數據)ui

在理論上的最壞狀況下,所須要的時間複雜度爲O(mn)spa

對於這樣的複雜度,小於十萬的數據或許還行,大於十萬就很是棘手了

所以咱們須要可以進行較大規模數據的數據結構。

例如:線段樹。

線段樹本質上是維護下標爲1,2,..,n的n個按順序排列的數的信息,因此,實際上是「點樹」,是維護n的點的信息,至於每一個點的數據的含義能夠有不少,

在對線段操做的線段樹中,每一個點表明一條線段,在用線段樹維護數列信息的時候,每一個點表明一個數,但本質上都是每一個點表明一個數。如下,在討論線段樹的時候,區間[L,R]指的是下標從L到R的這(R-L+1)個數,而不是指一條連續的線段。只是有時候這些數表明實際上一條線段的統計結果而已。

線段樹是將每一個區間[L,R]分解成[L,M]和[M+1,R] (其中M=(L+R)/2 這裏的除法是整數除法,即對結果下取整)直到 L==R 爲止。

下面咱們用一張圖來演示線段樹對於區間的劃分過程

 

 

以長度爲13的序列舉例

劃分過程以下

 

 

如上圖,每個節點都表明一段區間的信息

這就是區間樹。能夠證實,二叉區間樹的深度爲logn(本文中全部的log均爲log2

由於在這個樹上,咱們最終將這個區間都劃分紅了長度爲1的區間

所以實際上咱們要對點進行操做時或對區間進行操做時,只須要不斷劃分區間便可。

由於當咱們在區間樹上找到一個區間屬於當前要操做的區間時,其從屬的區間沒有必要進行查找,那麼顯然在區間樹上查詢一個區間的值的時間複雜度是log級別的

這樣的話咱們就能將單點操做和區間操做的時間複雜度進行均攤,達到log級別。

 

由於線段樹實際是以點來表示區間的,即:每個點上所記錄的實際是對應區間的信息

一下對與須要對二叉樹的點進行說明時,用「區間」代替點

那麼咱們來口頭實現一下最初要求實現的操做:

對單個節點進行修改,咱們不斷遞歸進行二分,知道枚舉到對應的葉子節點,而後修改便可

對於區間修改來講,咱們須要經過遞歸找到全部對應的區間(包括對應區間的子區間),而後進行修改

對於區間查詢來講,只須要找到對應的區間便可

下面放一下代碼?

我仍是放具體題目而後丟大段代碼吧…優秀板子我將放出某gy神的博客的連接…高端代碼自取系列:http://www.cnblogs.com/hinanawitenshi/p/8093624.html

懶標記

咱們先對代碼進行一下分析:

對於操做:

單點修改只是修改樹上的一條路徑,長度最長爲logn,所以複雜度爲O(logn)

對於區間和最大(小)值查詢,實際上仍是對一條路徑進行搜索,並且要用到的長度更小,就算極限,複雜度也只是O(logn)

可是對於區間修改,咱們一次修改的是一個區間的全部點,最壞狀況下,咱們要對1—n全部的數都進行修改,實際是修改了整棵樹,好像複雜度並無獲得改善,那麼咱們就得另想他法了:

仔細想一想好像沒有什麼辦法,由於要修改的話彷佛必須遍歷全部的數,可是實際上,咱們爲何必定要進行修改呢,仔細想一想,對於一個區間,咱們除了在對他的左右兒子區間進行查詢而進行劃分時,會須要他的左右兒子區間,可是其他時刻咱們好像並不須要用到,那麼咱們不妨只對這個區間對應的值進行修改,而後咱們打上一個標記,表示若是須要劃分這個區間,那麼這個區間的左右兒子的值也須要修改。這個標記,咱們叫作懶標記

那麼這樣的話咱們是否達到了優化區間修改的做用呢?

  對於區間修改來講,咱們僅將原先要修改的子樹的根節點打上了懶標記,而後返回,所以其時間複雜度與區間的詢問是相同的,即O(logn)

  對於區間詢問來講,咱們只是在遍歷到帶有標記的節點時,纔對標記進行相關的處理,而關於標記的處理的複雜度顯然是O(1)的,對總體的時間複雜度不形成影響,即區間詢問的時間複雜度仍然是O(logn)

   建樹只是增長了標記的初始化,單點操做則不須要進行初始化,其時間複雜度不變

 這樣咱們就將線段樹一次修改的總體複雜度降到了O(logn)

 

#include <bits/stdc++.h>
#define maxn 100057
using namespace std;
struct tre{
int delta,maxx,sum;
}tree[maxn];
int n,m;
int a[maxn];
void pushdown(int pos,int l,int r){//懶標記下傳 
  if(!tree[pos].delta)return;
  int lc=pos*2,rc=pos*2+1,m=(l+r)/1,vv=tree[pos].delta;
  tree[lc].sum+=(m-l+1)*vv,tree[rc].sum+=(r-m+1)*vv;
  tree[lc].maxx+=vv,tree[rc].maxx+=vv;
  tree[lc].delta+=vv,tree[rc].delta+=vv;
  tree[pos].delta=0;
}
void maintain(int pos){//從新計算區間的最大值和區間和 
  int lc=pos*2,rc=pos*2+1;
  tree[pos].maxx=max(tree[lc].maxx,tree[rc].maxx);
  tree[pos].sum=tree[lc].sum+tree[rc].sum;
}
void build(int pos,int l,int r){//建樹 
  if(l==r){
    tree[pos].delta=0;
    tree[pos].maxx=tree[pos].sum=a[l]; return;

  }
  int m=(l+r)/2;
  build(pos*2,l,m);
  build(pos*2+1,m+1,r);
  maintain(pos);
}
int query_max(int pos,int L,int R,int l,int r){//區間最大值查詢 
  if(l>R||r<L) return 0;
  if(l>=L&&r<=R) return tree[pos].maxx;
  pushdown(pos,l,r);
  int m=l+r>>1;
  return max(query_max(pos<<1,L,R,l,m),query_max((pos<<1)+1,L,R,m+1,r));
}
int query_sum(int pos,int L,int R,int l,int r){//區間和查詢 
  if(l>R||r<L) return 0;
  if(l>=L&&r<=R) return tree[pos].sum;
  pushdown(pos,l,r);
  int m=l+r>>1;
  return query_sum(pos*2,L,R,l,m)+query_sum(pos*2+1,L, R,m+1,r);
}
void updata(int pos,int L,int R,int l,int r,int v){//區間修改 
  if(l>R||r<L) return;
  if(l>=L&&r<=R){//此大括號內容根據題意自定 
    tree[pos].delta+=v;
    tree[pos].maxx+=v; 
    tree[pos].sum+=(r-l+1)*v; 
    return;
  }
  pushdown(pos,l,r);
  int m=(l+r)/2;
  updata(pos*2,L,R,l,m,v);
  updata(pos*2+1,L,R,m+1,r,v);
  maintain(pos);
}
int main(){
  cin>>n>>m;
  for(int i=1;i<=n;i++)
    cin>>a[i];
  build(1,1,n);
  while(m--){
    int bj,x,y,k;
    cin>>bj;
    if(bj==1){
      cin>>x>>y>>k;
      updata(1,x,y,1,n,k);
    }
    else if(bj==2){
      cin>>x>>y;
      cout<<query_sum(1,x,y,1,n)<<endl;
    }
    else{
      cin>>x>>y
      cout<<query_max(1,x,y,1,n)<<endl;
    }
  }
  return 0;
}

 

 不要在乎這醜陋的碼風和莫名其妙的斜字體

 

 關於區間奇數位和偶數位和的求解

 對於一個區間的奇數位和偶數位,咱們能夠知道的是,區間總和減去奇數位和等於偶數位和

可是在維護時,區間有一些須要注意的問題

好比一個大區間

[1, 2, 3, 4, 5, 6]

二分後獲得[1, 2, 3]和[4, 5, 6]

顯然其中奇數位1,3,5.然而按照最初建樹時狀況來看,咱們記錄的是1, 3和4,6的值

那麼如何獲得5的值的,能夠用右邊區間的總和 減去所記錄的奇數位和

由這個例子推廣便可。

對於查詢,咱們要如何知道咱們在查的是奇數位和仍是偶數位和呢?

對於一個大區間[L, R]中,咱們查到了一個小區間[l, r]

若l - L是奇數,咱們能夠知道的是,從L到l中,有算上l的三個數,因此l就是整個區間的偶數位

而l對於小區間[l, r]來講,是奇數位,因此對於小區間[l, r]咱們就要取偶數位,不然就取奇數位

舉例代碼,20181025九校聯考T2

  1 #include<bits/stdc++.h>
  2 #define ll long long
  3 using namespace std;
  4 const int maxn = 500010;
  5 const int maxm = 1000010;
  6 const int mod = 1000000007;
  7 struct shiki {
  8     ll sum, delta, l;
  9 }tree[maxm << 3];
 10 ll c[maxn << 1];
 11 int n, m;
 12 ll fac[maxn], inv[maxn];
 13 ll ans_ma = 0, ans_mi = 0;
 14 
 15 inline ll read() {
 16     ll x = 0, y = 1;
 17     char ch = getchar();
 18     while(!isdigit(ch)) {
 19         if(ch == '-') y = -1;
 20         ch = getchar();
 21     }
 22     while(isdigit(ch)) {
 23         x = (x << 1) + (x << 3) + ch - '0';
 24         ch = getchar();
 25     }
 26     return x * y;
 27 }
 28 
 29 inline ll power(ll a, ll b) {
 30     ll res = 1;
 31     for(; b; b >>= 1) {
 32         if(b & 1) res = res * a % mod;
 33         a = a * a % mod;
 34     }
 35     return res;
 36 } 
 37 
 38 inline void init() {
 39     fac[1] = 1, inv[1] = 1;
 40     for(int i = 2; i <= maxn; ++i) {
 41         fac[i] = (fac[i - 1] * i) % mod;//階乘 
 42         inv[i] = power(fac[i], mod - 2) % mod;//逆元 
 43     }
 44 }
 45 
 46 inline ll C(int n, int m) {//n!/m!(n-m)! = n! * m!^mod-2 * (n - m)!^mod-2 
 47 return fac[n] * inv[m] % mod * inv[n - m] % mod;} 
 48 
 49 inline void maintain(int pos, int l, int r) {
 50     int lc = pos << 1, rc = pos << 1 | 1, mid = l + r >> 1;
 51     tree[pos].sum  = tree[lc].sum + tree[rc].sum;
 52     tree[pos].l = tree[lc].l + (((mid - l + 1) & 1) ? tree[rc].sum - tree[rc].l : tree[rc].l);
 53     tree[pos].sum %= mod, tree[pos].l %= mod;
 54 }
 55 
 56 inline void pushdown(int pos, int l, int r) {
 57     if(!tree[pos].delta) return;
 58     int lc = pos << 1, rc = pos << 1 | 1, mid = l + r >> 1, del = tree[pos].delta;
 59     tree[lc].sum += (mid - l + 1) * del, tree[lc].l += (mid - l + 2) / 2 * del;
 60     tree[rc].sum += (r - mid) * del, tree[rc].l += (r - mid + 1) / 2 * del;
 61     tree[lc].sum %= mod, tree[lc].l %= mod;
 62     tree[rc].sum %= mod, tree[rc].l %= mod;
 63     tree[lc].delta += del, tree[rc].delta += del;
 64     tree[pos].delta = 0;
 65 }
 66 
 67 void build(int pos, int l, int r){
 68     if(l == r) {
 69         tree[pos].sum = c[l];
 70         tree[pos].l = c[l];
 71         return;
 72     }
 73     int mid = l + r >> 1;
 74     build(pos << 1, l, mid); 
 75     build(pos << 1 | 1, mid + 1, r);
 76     maintain(pos, l, r);
 77 }
 78 
 79 void update(int pos, int L, int R, int l, int r, int val) {
 80     if(l > R || r < L) return ;
 81     if(l >= L && r <= R) {
 82         tree[pos].sum += (r - l + 1) * val;
 83         tree[pos].delta += val;
 84         tree[pos].l += (r - l + 2) / 2 * val;
 85         tree[pos].l %= mod, tree[pos].sum %= mod;
 86         return;
 87     }
 88     if(l != r) pushdown(pos, l, r);
 89     int mid = l + r >> 1;
 90     update(pos << 1, L, R, l, mid, val);
 91     update(pos << 1 | 1, L, R, mid + 1, r, val);
 92     maintain(pos, l, r);
 93 }
 94 
 95 ll query_sum(int pos, int L, int R, int l, int r) {
 96     if(l > R || r < L) return 0;
 97     if(l >= L && r <= R) return tree[pos].sum % mod;
 98     if(l != r) pushdown(pos, l, r);
 99     int mid = l + r >> 1;
100     return (query_sum(pos << 1, L, R, l, mid) + query_sum(pos << 1 | 1, L, R, mid + 1, r)) % mod;
101 }
102 
103 ll query_l(int pos, int L, int R, int l, int r) {
104     if(l > R || r < L) return 0;
105     if(l >= L && r <= R) 
106         return ((l - L) & 1) ? tree[pos].sum - tree[pos].l : tree[pos].l;
107     if(l != r) pushdown(pos, l, r);
108     int mid = l + r >> 1;
109     return (query_l(pos << 1, L, R, l, mid) + query_l(pos << 1 | 1, L, R, mid + 1, r)) % mod;
110 }
111 
112 int main() {
113     freopen("sort.in", "r", stdin);
114     freopen("sort.out", "w", stdout);
115     init();
116     n = read(), m = read();
117     for(int i = 1; i <= 2 * n; ++i) c[i] = read();
118 //    sort(c + 1, c + 2 * n + 1);
119     if(n <= 5100) {
120         for(int i = 1; i <= m; ++i) {
121             ll opt = read(), l = read(), r = read();
122             if(l > r) swap(l, r);
123             if(opt == 0) {
124                 ll val = read();
125                 for(int j = l; j <= r; ++j)
126                     c[j] += val;
127             }
128             else if(opt == 1) {
129                 ll ln = (r - l + 1), lm = (r - l + 1) >> 1, mid = (l + r) >> 1;
130                 ll op = C(ln, lm) % mod * power(lm + 1, mod - 2) % mod;
131                 ans_ma = 0, ans_mi = 0;
132                 for(int j = l; j <= mid; ++j) ans_ma -= c[j];
133                 for(int j = mid + 1; j <= r; ++j) ans_ma += c[j];
134                 for(int j = l; j <= r; j += 2) ans_mi -= c[j];
135                 for(int j = l + 1; j <= r; j += 2) ans_mi += c[j];
136                 printf("%lld %lld %lld\n", ans_ma, ans_mi, op);
137             }
138         }
139         return 0;
140     }
141     else {    
142         n = 2 * n;
143         build(1, 1, n);
144         for(int i = 1; i <= m; ++i) {
145             ll opt = read(), l = read(), r = read();
146             if(l > r) swap(l, r);
147             if(opt == 0) {
148                 ll val = read();
149                 update(1, l, r, 1, n, val);
150             }
151             else if(opt == 1) {
152                 ll ln = (r - l + 1), lm = (r - l + 1) >> 1, mid = (l + r) >> 1;
153                 ll op = C(ln, lm) % mod * power(lm + 1, mod - 2) % mod;
154                 ans_ma = (query_sum(1, mid + 1, r, 1, n) - query_sum(1, l, mid, 1, n) + mod) % mod;
155                 ans_mi = (query_l(1, l + 1, r, 1, n) - query_l(1, l, r, 1, n) + mod) % mod;
156                 printf("%lld %lld %lld\n", ans_ma, ans_mi, op);
157             }
158         }
159     }
160     return 0;
161 }
相關文章
相關標籤/搜索