學習筆記——線段樹
阿新 • • 發佈:2022-01-19
線段樹
Question
用一個數據結構維護一個數列,支援:
- 單點/區間修改
- 區間求和/最大值/最小值/...
DateStructure:線段樹
整體思想
線段樹就是一大段分成兩小段,對於小段再繼續分割,更新時更新父節點,查詢時合併區間的查詢。
操作方法
單點修改
從最大區間開始一直尋找包含修改點的區間,同時對包含此點區間進行值的修改。
區間查詢
從最大區間開始一直遍歷兩個子區間,每個區間有三種情況:
- 與查詢區間完全不相交,此時直接跳出。
- 被查詢區間完全包含,此時返回此區間的值。
- 1,2都不滿足,此時繼續遍歷兩個子區間。
Lazy Tag
當進行區間修改的時候,如果一次性更新到最底層的節點時,時間就會變成 \(O(n)\)
這時候我們就要打“懶標記”了。
當一個區間完全被包含在修改區間內的時候,就給他打上標記,當查詢到它的子區間時再去下傳標記。
Code
單點查詢和更改更簡單一些,會了區間肯定能實現出來,這裡就只粘上區間更改和查詢的板子了。
其實就是懶。
void pushdown(ll p){//下傳標記 if(!t[p].bj)return; t[p*2].bj+=t[p].bj,t[p*2].val+=t[p].bj*(t[p*2].r-t[p*2].l+1); t[p*2+1].bj+=t[p].bj,t[p*2+1].val+=t[p].bj*(t[p*2+1].r-t[p*2+1].l+1); t[p].bj=0; //先打標記,再更新值 }ll build_tree(ll p,ll l,ll r){// t[p].l=l,t[p].r=r; if(l==r)scanf("%lld",&t[p].val); //最底層的點 else{ ll mid=(l+r)>>1; t[p].val=build_tree(p*2,l,mid)+build_tree(p*2+1,mid+1,r); //建兒子 }return t[p].val; }void update(ll p,ll l,ll r,ll d){//區間更新 ll le=t[p].l,ri=t[p].r; if(le>r||ri<l)return; if(le>=l&&ri<=r)t[p].val+=d*(t[p].r-t[p].l+1),t[p].bj+=d; //打上標記 else{ pushdown(p),update(p*2,l,r,d),update(p*2+1,l,r,d); t[p].val=t[p*2].val+t[p*2+1].val; //給兒子下傳之前的標記 } }ll query(ll p,ll l,ll r){//查詢 ll le=t[p].l,ri=t[p].r,ans; if(le>r||ri<l)return 0; if(le>=l&&ri<=r)return t[p].val; pushdown(p);//下傳標記 return ans=query(p*2,l,r)+query(p*2+1,l,r); }
Example:山海經
因為教練給我們留的六道題中五道都是裸板子,所以只能直接放毒瘤題了。
Meaning of the Problem
給你一個序列,每次查詢區間 \((x,y)\) 的最大子段和。
Solution
我們要維護一堆東西:
- 區間和
- 最大字首和其右端點
- 最大子段和其左右端點
- 最大字尾和其左端點
為什麼這麼麻煩呢?
因為我們要通過合併來維護子段和。
- 對於一個區間的最大字首和有兩種情況:
- 左子區間的最大字首和。
- 左子區間和加右子區間的最大字首和。
- 對於一個區間的最大字尾和有兩種情況:
- 右子區間的最大字尾和。
- 右子區間和加左子區間的最大字尾和。
- 對於一個區間的最大子段和有三種情況:
- 左子區間的最大子段和。
- 右子區間的最大子段和。
- 左子區間的最大字尾和加右子區間的最大字首和。
只要我們把板子裡的"求和操作"改成"合併操作"就可以了。
這道題很噁心(對於線段樹初學者來說),很適合鍛鍊程式碼能力,我寫+調了一個早上才過,同機房的大佬們只有兩個調到了晚上才過,大多數人還在調……
Code
#include <bits/stdc++.h>
#define _for(i,a,b) for(int i=a;i<=b;++i)
#define for_(i,a,b) for(int i=a;i>=b;--i)
#define ll long long
using namespace std;
const int N=1e5+10,inf=0x3f3f3f3f;
ll n,m,x,y,a[N],ans;
struct stru{
ll val,l,r;//區間與值
ll qz,qr,hz,hl;//維護前後綴
ll zz,zl,zr;//維護中綴
#define ls p<<1
#define rs p<<1|1
}t[4*N];
inline ll read(){
int x=0,w=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();}
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+c-48,c=getchar();
return w*x;
}stru merge(stru a,stru b){//合併
stru p;//合併結果區間
//特判
if(a.l==0&&a.r==0&&b.l==0&&b.r==0)return stru{0,0,0,0,0,0,0,0,0,0};
if(a.l==0&&a.r==0)return b;
if(b.l==0&&b.r==0)return a;
//左右端點
p.l=a.l,p.r=b.r;
//區間和
p.val=a.val+b.val;
//字首
if(a.val+b.qz>a.qz)p.qz=a.val+b.qz,p.qr=b.qr;
else p.qz=a.qz,p.qr=a.qr;
//字尾
if(b.val+a.hz<b.hz) p.hz=b.hz,p.hl=b.hl;
else p.hz=b.val+a.hz,p.hl=a.hl;
//中綴
int qjh=a.hz+b.qz;//前加後
if(a.zz>=qjh&&a.zz>=b.zz)p.zz=a.zz,p.zl=a.zl,p.zr=a.zr;
else if(qjh>=b.zz)p.zz=qjh,p.zl=a.hl,p.zr=b.qr;
else p.zz=b.zz,p.zl=b.zl,p.zr=b.zr;
return p;
}void build_tree(int p,int l,int r){
#define f t[p]
f.l=l,f.r=r;
if(l==r){
f.zz=f.val=read();
f.qz=f.hz=f.val;
f.qr=f.zl=f.zr=f.hl=l;
}else{
int mid=(l+r)>>1;
build_tree(ls,l,mid),build_tree(rs,mid+1,r);
f=merge(t[ls],t[rs]);
}
}stru query(int p,int l,int r){
int le=t[p].l,ri=t[p].r;
stru tmp=stru{0,0,0,0,0,0,0,0,0,0};
if(le>r||ri<l)return tmp;
if(le>=l&&ri<=r)tmp=t[p];
else tmp=merge(query(p*2,l,r),query(p*2+1,l,r));
return tmp;
}
int main(){
freopen("date.in","r",stdin);
n=read(),m=read();
build_tree(1,1,n);
while(m--){
x=read(),y=read();stru ans=query(1,x,y);
printf("%lld %lld %lld\n",ans.zl,ans.zr,ans.zz);
}
return 0;
}
本文來自部落格園,作者:Keven-He,轉載請註明原文連結:https://www.cnblogs.com/Keven-He/p/15820082.html