今天來仔細地說一下線段樹
線段樹可以高效率地解決許許多多的區間操作
比如區間求和,把一個區間中所有的數加上常量k,區間求最大值最小值等等
定義
線段樹是一個完全二叉樹
它在各個節點保存一條線段(數組中的一段子數組)
每個單元區間對應線段樹中的一個葉結點
性質:父親的區間是[l,r],(m=(l+r)/2)左兒子的區間是[l,m],右兒子的區間是[m+1,r],線段樹需要的空間爲數組大小的四倍
基本操作
這裏舉的是區間加與區間求和的例子
先列出來
1. 建線段樹
2. 區間加
3. 區間查詢和
建樹
- 首先,需要一個結構體,來保存一個節點
struct node
{
int left, right;
//表示這個節點能夠在數組中管轄的線段的範圍是從left到right
int s;
//表示這條線段的區間和
node *ch[2];
//節點的兩個孩子,ch[0]爲左孩子,ch[1]是右孩子
}pool[MAXN], *root;//pool是內存池,root是根節點
- 這樣就能夠用node來表示線段樹的節點(但這還不夠,詳看區間加)
- 接下來是建樹操作
void Build_Tree(node *r, int left, int right)
//遞歸建樹,指以r爲根節點建線段爲left至right的子樹
{
r->left = left;
r->right = right;
//將r->left更新爲left,r->right更新爲right
if(left == right)
//如果遞歸到了葉子節點
{
r->s = a[left];
//單個葉子節點的區間和就是數組中的那個值
return ;
}
int mid = (left + right) / 2;//詳見性質
node *lson = &pool[++cnt];//申請空間
node *rson = &pool[++cnt];//申請空間
r->ch[0] = lson;//讓r的左子指向lson
r->ch[1] = rson;//讓r的右子指向rson
Build_Tree(lson, left, mid); //遞歸建樹,詳見性質
Build_Tree(rson, mid + 1, right);//遞歸建樹,詳見性質
r->s = r->ch[0]->s + r->ch[1]->s;//維護和
}
區間加
即給定一個區間left,right和一個需要加上的值
完成操作:讓left ~ right的所有數加上一個d
- 暴力操作:一個一個點進行單點修改,複雜度
- 顯然複雜度不滿足要求
- 所以,我們換一種方式
- 在每一個節點中加入一個懶標記,表示這個整個區間每一個數需要加上這個懶標記
- 比如說要給 加上 ,那麼就讓表示 的區間的 (懶標記)加上
- 我們需要一個pushdown函數,把一個節點的懶標記轉換成區間和並且下發到自己的左右兩子
pushdown:
inline void Push(node *r)
{
if(r->lazy == 0) return ; //沒有需要加的值直接return
r->s += (r->right - r->left + 1) * r->lazy;
//區間和加上區間的長度(right - left + 1)乘上每個數需要加上的值lazy
if(r->ch[0]) r->ch[0]->lazy += r->lazy;
if(r->ch[1]) r->ch[1]->lazy += r->lazy;
//左右兩子的lazy加上自己的lazy
r->lazy = 0;
}
修改函數:
void change(node *r, int left, int right, int d)
{
//d是要加上的數
if(r->left == left && r->right == right)
//找到了要修改的區間
{
r->lazy += d;//把每個數要加的值lazy加上d
return ;
}
Push(r);
//發放lazy
if(r->ch[0]->right >= right) change(r->ch[0], left, right, d);
//如果左子的右端點比要查找的右端點大,則在左子中繼續修改
else if(r->ch[1]->left <= left) change(r->ch[1], left, right, d);
//如果右子的左端點比要查找的左端點小,則在右子中繼續修改
{
change(r->ch[0], left, r->ch[0]->right, d);
change(r->ch[1], r->ch[1]->left, right, d);
//否則把需要查找的區間砍成兩半,分別在左右端點分別修改
}
r->s = r->ch[0]->s + r->ch[1]->s
//因爲修改了值,所以需要重新維護s
}
區間和
- 給你一個區間 ,求
- 與區間修改相似,給一個代碼
int query(node *r, int left, int right)
{
Push(r);//最開始Push,免得返回時再算一遍
if(r->left == left && r->right == right) return r->s;
if(r->ch[0]->right >= right) return query(r->ch[0], left, right);
else if(r->ch[1]->left <= left) return query(r->ch[1], left, right);
else
return query(r->ch[0], left, r->ch[0]->right) +
query(r->ch[1], r->ch[1]->left, right);
}