1. 程式人生 > >資料結構——線段樹

資料結構——線段樹

線段樹是一種基於分治思想的類似於二叉樹的資料結構,一般用於陣列的資訊統計,相比於樹狀陣列,線段樹有著更廣闊的應用空間,但是相對的其程式碼量長,且常數大


一.

首先我們來講線段樹的建樹過程,請看下圖:

 

這張圖就是線段樹的儲存結構,我們從最長的區間開始依次分成兩部分,每一部分都有一個需要維護的權,建樹過程比較簡單,程式碼如下:

inline void build(int l,int r,int rt) //l表示當前的左端點,r表示右端點,rt是當前區間的編號
{
  if(l == r) //當左右端點相同,說明當前的區間是一個點,給予該點一個初值
  {
    scanf(
"%d",c[rt]); return ; } int m = (l + r) >> 1; build(l,m,rt<<1); build(m + 1,r,rt<<1|1); //分成兩個區間繼續進行建樹操作 update(rt); //更新當前區間的值 return ; } build(1,n,1);

 

關於上面的update函式,一般是根據你需要維護的條件進行的,下面給出幾個例子:

inline void update(int rt)
{
  c[rt] = c[rt<<1
] + c[rt<<1|1]; //維護區間和 c[rt] = std::max(c[rt<<1],c[rt<<1|1]);//維護區間最大值 c[rt] = std::min(c[rt<<1],c[rt<<1|1]);//維護區間最小值 return ; }

現在建樹過程大概都瞭解了,我們開始講查詢和修改操作

 

首先我們先講單點修改,具體操作就是從根節點開始,每次比較我們插入的位置與中點位置的大小,從而選擇左右區間,做法類似於二分,在這裡不多講,程式碼如下:

inline void modify(int
l,int r,int rt,int x,int y) //給x加上y { if(l == r) { c[rt] += y; return ; } int m = (l + r) >> 1; if(x <= m) modify(l,m,rt<<1,x,y); else modify(m + 1,r,rt<<1|1,x,y); update(rt); return ; } modify(1,n,1,x,y);

 

單點查詢操作的實現在做法上與單點修改一樣,程式碼如下:

inline void query(int l,int r,int rt,int p) //p為我們需要查詢的點
{
  if(l == r && l == p)
  {
    ans = c[rt];
    return ;
  }
  int m = (l + r) >> 1;
  if(p <= m) query(l,m,rt<<1,p);
  else query(m + 1,r,rt<<1|1,p);
  return ;
}

query(1,n,1,p);

 

然後我們考慮區間查詢操作,我們可以發現,當我們要查詢的區間的左端點小於等於中點時,那麼它與左區間一定有交集,我們就可以進入左子樹查詢,相應的,當它的右端點大於中點時,與右區間有交集,我們進入右子樹查詢,並且當我們當前的區間左端點大於等於查詢區間左端點,且右端點小於等於查詢區間右端點時,那麼這個區間一定在查詢區間內,我們就直接用該區間權值維護答案,不必再向下搜尋,於是我們得到區間查詢的方法,程式碼如下:

inline void query(int l,int r,int rt,int nl,int nr) //nl,nr為當前需要查詢的區間
{
  if(nl <= l &&r <= nr)
  {
    ans += c[rt]; //這裡我們用求區間和舉例
    return ;
  }
  int m = (l + r) >> 1;
  if(nl <= m) query(l,m,rt<<1,nl,nr);
  if(nr > m) query(m + 1,r,rt<<1|1,nl,nr);
  return ;
}

query(1,n,1,nl,nr);

 


二.

關於區間修改,一個顯然的想法是,我們像區間查詢那樣做,但是在這裡有個問題,我們在修改區間的時候,如果整個區間都被覆蓋,那麼每一個節點都需要更新,複雜度會上升到O(n),因此,我們來考慮如何進行優化,在這裡,我們就引入延遲標記,當一個區間l ~ r被修改後,我們需在[l ~ r]這個區間加上一個 延遲標記,當我們在查詢的時候,如果當前區間的若干個子樹需要修改或查詢,我們就檢驗當前區間是否有延遲標記,然後下發標記,這時我們發現,區間修改的複雜度會降至O(nlogn)

下面給出區間修改和加法延遲標記的實現(這裡以區間查詢為例):

 

inline void sign(int l,int r,int rt,int v) //延遲標記
{
  c[rt] += (r - l + 1)*v;
  sg[rt] += v;
  return ;
}

inline void push_down(int l,int r,int rt) //下放標記
{
  if(sg[rt])
  {
    int m = (l + r) >> 1;
    sign(l,m,sg[rt]);
    sign(m + 1,r,sg[rt]);
    sg[rt] = 0;
  }
  return ;
}

inline void modify(int l,int r,int rt,int nl,int nr,int v) 
{
  if(nl <= l && r <= nr)
  {
    sign(l,r,rt,v);
    return ;
  }
  push_down(l,r,rt);
  int m = (l + r) >> 1;
  if(nl <= m) modify(l,m,rt<<1,nl,nr,v);
  if(nr > m) query(m + 1,r,rt<<1|1,nl,nr,v);
  return ;
}


inline void query(int l,int r,int rt,int nl,int nr) 
{
  if(nl<=l&&r<=nr)
  {
    ans += c[rt];
    return ;
  }
  push_down(l,r,rt);
  int m = (l + r) >> 1;
  if(nl <= m) query(l,m,rt<<1,nl,nr);
  if(nr > m) query(m + 1,r,rt<<1|1,nl,nr);
  return ;
}

modify(1,n,1,nl,nr,v);
query(1,n,1,nl,nr);

 

值得注意的是,在複雜的線段樹操作中可能會出現多種延遲標記,這時需要我們考慮運算順序,如一個線段樹中同時存在著加法標記和乘法標記,運算的順序不同,結果就可能會不同

關於延遲標記這裡給出兩道例題:

洛谷 P3372

洛谷 P3373

這兩道例題都非常的簡單,在這裡我給出第一個例題的程式碼,第二個例題可以參照第一題的做法

#include <cstdio>
#include <cstring>
#include <algorithm>
#define root 1,n,1
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define mid (l+r)>>1

const int maxn = 1e6 + 6;
int n,tm;
long long c[maxn];
int sg[maxn];
long long ans;

inline int read()
{
  char ch = getchar();int x = 0,f = 1;
  while(ch<'0'||ch>'9'){if(ch == '-') f = -1;ch = getchar();}
  while(ch>='0'&&ch<='9'){x = x*10 + ch -'0';ch = getchar();}
  return x*f;
}

inline void update(int rt)
{
  c[rt] = c[rt<<1] + c[rt<<1|1];
  return ;
}

inline void sign(int l,int r,int rt,int v)
{
  c[rt] += 1ll*(r-l+1)*v;
  sg[rt] += v;
  return ;
}

inline void push_down(int l,int r,int rt)
{
  if(sg[rt])
  {
    int m = mid;
    sign(lson,sg[rt]);
    sign(rson,sg[rt]);
    sg[rt] = 0;
  }
  return ;
}

inline void build (int l,int r,int rt)
{
  if(l==r)
  {
    c[rt] = read();
    return ;
  }
  int m = mid;
  build(lson);
  build(rson);
  update(rt);
}

inline void modify(int l,int r,int rt,int nl,int nr,int v)
{
  if(nl<=l&&r<=nr)
  {
    sign(l,r,rt,v);
    return ;
  }
  push_down(l,r,rt);
  int m = mid;
  if(nl<=m) modify(lson,nl,nr,v);
  if(nr>m) modify(rson,nl,nr,v);
  update(rt);
}

inline void query(int l,int r,int rt,int nl,int nr)
{
  if(nl<=l&&r<=nr)
  {
    ans += c[rt];
    return ;
  }
  push_down(l,r,rt);
  int m = mid;
  if(nl<=m) query(lson,nl,nr);
  if(nr>m) query(rson,nl,nr);
  update(rt);
}

int main(int argc, char const *argv[]) 
{
  n = read();
  tm = read();
  build(root);
  for(int i = 1;i <= tm;i ++)
  {
    int flag,x,y,k;
    flag = read();
    if(flag == 1)
    {
      x = read();
      y = read();
      k = read();
      modify(root,x,y,k);
    }
    else
    {
      ans = 0;
      x = read();
      y = read();
      query(root,x,y);
      printf("%lld\n",ans);
    }
  }
  return 0;
}