線段樹入門
阿新 • • 發佈:2020-11-28
把我csdn的blog搬過來的((將就看
線段樹
想學主席樹,然後開了一篇題解,看到“學習可持久化線段樹之前一定要學懂線段樹”這句話我只好把線段樹又學了一遍((
這塊從昨天到今天學了四個小時,做了三頁紙筆記,所以有錯的可能性不大(但是wtcl,所以還是有很大可能的
概括
線段樹的理解,按照書上的((
- 線段樹的每個節點都代表一個區間;
- 線段樹具有唯一的根節點,代表的區間是整個統計範圍;
- 線段樹的每個葉節點都代表一個長度為1的元區間[x,x];
- 對於每個內部節點[l,r],它的左子節點是[l,mid],右子節點是[mid+1,r],其中mid=(l+r)/2;
圖我就不放了
另一個點就是”父子2倍“,即節點i的權值=左兒子權值+右兒子的權值,舉個栗子,節點i的權值編號p,左兒子權值即p*2,右兒子權值即p * 2+1(應該沒說錯吧;
建樹
先講一下線段樹的建樹。
struct陣列儲存我就先略過了;
上程式碼:
void build (int p,int l,int r) { t[p].l=l,t[p].r=r; //節點p代表區間[l,r]; if (l==r){ t[p].dat=a[l]; return; //葉節點; } int mid=(l+r)/2; //折半(根據“父子二倍”,求mid; build(p*2,l,mid); //構造左子節點,即[l,mid],編號p*2; build(p*2+1,mid+1,r); //同上一步,構造右子節點,即[mid+1,r],編號p*2+1;t[p].dat=max(t[p*2].dat,t[p*2+1].dat); //從下往上傳遞資訊; } build (1,1,n); //呼叫入口;
單點修改
所謂單點修改,就是:
- 從根節點出發,遞迴找到代表區間[x,x]的葉節點;
- 更新(從下往上)[x,x]以及其所有祖先節點上的資訊;
- 時間複雜度為O(logN);
上程式碼:
void change (int p,int x,int v) { if (t[p].l==t[p].r) { t[p].dat=v; return; //找葉節點; } int mid=(t[p].l+t[p].r)/2; //折半; if (x<=mid) //找x所屬的區間;change(p*2,x,v); else change(p*2+1,x,v); t[p].dat=max(t[p*2].dat,t[p*2+1],dat); } change(1,x,v); //呼叫入口;
區間查詢
遞迴:
- [l,r]完全覆蓋了當前節點代表的區間,回溯,該節點的dat值為候選答案;
- 左子節點與[l,r]有重疊部分,遞迴訪問左子節點;
- 右子節點與[l,r]有重疊部分,遞迴訪問右子節點;
程式碼:
int ask(int p,int l,int r) { if (l<=t[p].l &&r>=t[p].r) return t[p].dat; //完全包含; int mid=(t[p].l+t[p].r)/2; int val=a(1<<30); if (l<=mid) val=max(val,ask(p*2,l,r)); //與左子節點有重疊; if (r>mid) val=max(val,ask(p*2+1,l,r)); //與右子節點有重疊; return val; } cout<<ask(1,l,r)<<endl; //呼叫入口;
還有兩個點,即區間修改&延遲標記;
區間修改就是結合單點修改和區間查詢…
延遲標記是“在回溯前向節點p加一個標記,標識“該節點曾被修改,但子節點尚未更新””。
這邊就不敲程式碼了((我太菜了怕出錯
總結
有點虎頭蛇尾,畢竟是自己對線段樹的總結可能會有誤;
本篇blog應該適用於線段樹入門QAQ
謝謝大家耐心看完awa