線段樹模板整理
阿新 • • 發佈:2019-01-28
綜述
線段樹的原理:將[1,n]分解成若干特定的子區間(數量不超過4*n),然後,將每個區間[L,R]都分解為少量特定的子區間,通過對這些少量子區間的修改或者統計,來實現快速對[L,R]的修改或者統計。
作用:對編號連續的一些點的區間資訊進行修改或者統計操作
主要操作:區間查詢、點更新、區間更新
時間複雜度:修改和統計的複雜度都是O(log(N))
由原理可以看出線段樹維護的資訊必須滿足區間加法
如:
數字之和——總數字之和 = 左區間數字之和 + 右區間數字之和
最大公因數(GCD)——總GCD = gcd( 左區間GCD , 右區間GCD );
最大值——總最大值=max(左區間最大值,右區間最大值)
線段樹原理的詳細分析及應用可以參考一篇寫得特別好的博文:線段樹詳解
這篇部落格完全可以作為學習線段樹的指南及訓練線段樹的參考。
模板
為了規範自己的寫法,所以就整理一下模板。
以下模板ans[]存的是區間和,若存其他符合區間加法的資訊,需要相應改程式碼。
(0)定義
const int MAXN=50010;
int a[MAXN],ans[MAXN<<2],lazy[MAXN<<2];
//a[]為原序列資訊,ans[]模擬線段樹維護區間和,lazy[]為懶惰標記
(1)更新結點資訊
void PushUp(int rt)
{
ans[rt]=ans[rt<<1 ]+ans[rt<<1|1];
}
(2)建樹
void Build(int l,int r,int rt)
{
if (l==r)
{
ans[rt]=a[l];
return;
}
int mid=(l+r)>>1;
Build(l,mid,rt<<1);
Build(mid+1,r,rt<<1|1);
PushUp(rt);
}
(3) 下推懶惰標記
void PushDown(int rt,int ln,int rn)//ln表示左子樹元素結點個數,rn表示右子樹結點個數
{
if (lazy[rt])
{
lazy[rt<<1]+=lazy[rt];
lazy[rt<<1|1]+=lazy[rt];
ans[rt<<1]+=lazy[rt]*ln;
ans[rt<<1|1]+=lazy[rt]*rn;
lazy[rt]=0;
}
}
(4)點更新
void Add(int L,int C,int l,int r,int rt)
{
if (l==r)
{
ans[rt]+=C;
return;
}
int mid=(l+r)>>1;
//PushDown(rt,mid-l+1,r-mid); 若既有點更新又有區間更新,需要這句話
if (L<=mid)
Add(L,C,l,mid,rt<<1);
else
Add(L,C,mid+1,r,rt<<1|1);
PushUp(rt);
}
(5)區間更新
void Update(int L,int R,int C,int l,int r,int rt)
{
if (L<=l&&r<=R)
{
ans[rt]+=C*(r-l+1);
lazy[rt]+=C;
return;
}
int mid=(l+r)>>1;
PushDown(rt,mid-l+1,r-mid);
if (L<=mid) Update(L,R,C,l,mid,rt<<1);
if (R>mid) Update(L,R,C,mid+1,r,rt<<1|1);
PushUp(rt);
}
(6)區間查詢
LL Query(int L,int R,int l,int r,int rt)
{
if (L<=l&&r<=R)
return ans[rt];
int mid=(l+r)>>1;
PushDown(rt,mid-l+1,r-mid);//若更新只有點更新,不需要這句
LL ANS=0;
if (L<=mid) ANS+=Query(L,R,l,mid,rt<<1);
if (R>mid) ANS+=Query(L,R,mid+1,r,rt<<1|1);
return ANS;
}
(7)呼叫函式
//建樹
Build(1,n,1);
//點更新
Add(L,C,1,n,1);
//區間修改
Update(L,R,C,1,n,1);
//區間查詢
int ANS=Query(L,R,1,n,1);
注:若只涉及點更新的題,只需用(1)(2)(4)(6)
若只涉及區間更新的題,需用(1)(2)(3)(5)(6)
若為兩種更新都有,則在所有向子區間查詢或更新前,都需PushDown()