目錄
- 線段樹
- 簡介
- 節點
- 加法線段樹
- 1. 準備變數
- 2. 上拉操作
- 3. 建樹
- 4. 懶標記
- 5. 下放操作
- 6. 區間修改
- updata
- 異或線段樹
- pushup
- updata
- 最值線段樹
- updata
- pushup
線段樹
簡介
- 線段樹(一個二叉樹)是一個非常重要的資料結構,利用分治的思想。可以用於維護一些滿足結合律區間的資訊,例如區間元素之和,區間異或和。
- 線段樹可以實現O(logn)(需要Lazy tag,樸素版只允許單點修改和區間查詢)的時間複雜度的區間修改和區間查詢,與樹狀陣列相比,它更具有通用性,但是常數略大。
節點
線段樹由多個節點組成,每個節點包含以下資訊
- 編號: O
- 管轄區間: [st,ed]
- 權值: 和/最值/異或和/(等滿足性質資料)
加法線段樹
字首和在查詢與加法交替出現時複雜度很高,不適用
此處可使用樹狀陣列,但是線段樹可擴充性更強
因為線段樹一定是二叉樹,所以一般利用二叉樹編號性質構造整棵樹.對於編號為x的節點,左兒子為2x,右兒子為2x+1.所以不需要記錄左右兒子,但是也使得需要開四倍n的陣列才能存下整棵樹,一般以1號節點為根.
樹的大小為1時為葉節點,葉節點組成原陣列
線段樹每次操作從根節點開始,每個節點的管轄區間可以由根算出來s
1. 準備變數
#define int long long
const int N = 2e5+10;
int a[N];//原陣列
int n;//原陣列大小
int t[N << 2];//開四倍空間,t[x]表示節點x所表示的區間元素大小
2. 上拉操作
purhup(上拉操作)是所有線段樹所有操作都需要用到的工具函式,是用兒子資訊來更新自己的資訊.
void pushup(int o)
{
t[o] = t[o<<1] + t[0<<1 | 1];//可能不是加法
}
3. 建樹
建樹採用分治思想, 分別將左右子樹建立,然後利用pushup來更新當前節點.
void bulidTree(int s = 1,int e = n,int o = 1)
{
if(s == e)
{
t[o] = a[s];
return ;
}
int mid = s + e >> 1;
bulidTree(s,mid,o<<1),bulidTree(mid+1,e,o<<1 | 1);
pushup(o);
}
4. 懶標記
懶標記(lazytag)用於表示某個節點尚未更新給子節點的值, 懶標記是保證線段樹的區間修改和區間查詢的複雜度正確性的關鍵.
int lz[N<<2];
//lz[o]表示: 節點o還有lazy[o]這麼大的一個數字,還沒有加給左右兒子.
當lz[o] == 0,說明當前節點的左右兒子都已經被更新,得到了正確的值,使用懶標記必須配合下放操作
5. 下放操作
pushdown(下放操作)需要三個引數, 分別是當前的操作區間([st,ed])和節點編號(O).
void pushdown(int s,int e,int o)
{
if(!lz[o]) return ;//如果lz[o] == 0,無需下放
int mid = s + 1 >> 1,ls = 0<<!,rs = o<<1 | 1;//ls,rs為左右兒子編號
t[ls] += lz[o] * (mid - s + 1);//可改為updata()
t[rs] += lz[o] * (e-mid);
//每個t(區間和)都需要乘上一個區間長度.
lz[ls] += lz[o],lz[rs] += lz[o];//標記也要下放
lz[o] = 0;//lz下放完畢
}
6. 區間修改
將區間[l,r]中的數字都加上x,採用遞迴形式,當走到了目標區域內,就將對應的t[o]和lz[o]做出修改.
此函式是線段樹精髓
void add(int l,int r,int x,int s = 1,int e = n,int o = 1)
{
if(l <= s && e <= r)
{
//當前操作區間已經完全進入目標區間,應該直接被修改並且打算lz標記,不再向下
t[o] += (e-s+1)*x;
lz[o] += x;
return;
}
pushdown(e,s,o);//向下走前,必須下放
int mid = s + e >> 1;
if(mid >= l) add(l,r,x,s,mid,o<<1);//判斷是否需要往左走
if(mid + 1 <= r) add(l,r,x,mid+1,e,o<<1 | 1);//判斷是否需要往右走
pushup(o);//遞迴回來,上拉
}
updata
void updata(int s,int e,int o,int x)
{
t[o] += (e-s+1)*x;
lz[o] += x;
}
異或線段樹
只需要修改"pushup"和"updata"
pushup
將"+"改為"^"即可
void pushup(int o)
{
t[o] = t[o<<1] ^ t[o<<1 | 1];
}
updata
void updata(int s,int e,int o,int x)
{
t[o] ^= ((e - s + 1)&1) ? x : 0;// 奇數才異或x
//因為偶數次異或等同於未操作
lz[o] ^= x;
}
最值線段樹
updata
將區間[s,e],節點o加上x
void updata(int s,int e,int o,int x)
{
tmax[o] += x,tmin[o] += x;
lz[o] += x;
}
pushup
void pushup(int o)
{
tmax[o] = max(tmax[o<<1],tmax[o<<1 | 1]);
tmin[o] = min(tmin[o<<1],tmin[o<<1 | 1]);
lz[o] += x;
}