数据结构(终极线段树篇)
數據結構(終極線段樹篇)
摘要:
問題的提出:如何解決多樣化的區間操作問題?
solve:線段樹!!!
關鍵字:
線段樹,可持久化線段樹,權值線段樹,線段樹森林,動態開點線段樹,區間操作,線段樹應用。
前言:
區間操作問題的解決方法極多,如:樹狀數組,RMQ等。
所有這些數據結構都具有一定的局限性,都具有各自的優勢和劣勢。
而線段樹無疑是這些數據結構中性價比最高的數據結構!(Ps:搞什么線段樹,暴力出奇跡啊!)
線段樹,線段樹,線段樹。
顧名思義:樹上每一個節點表示一個線段(區間),每一次對一整個線段(區間)進行操作,棒棒。
? ? ? ? 第1節講述了線段樹的基本性質。
? ? ? ? 第2節實現了基本線段樹。
? ? ? ? 第3節講述動態開點線段樹。
? ? ? ? 第4節講述權值線段樹。
? ? ? ? //第5節講述線段樹森林。
? ? ? ? //第6節講述線段樹的重要應用和例題。
?
1.線段樹的基本性質:
?? ? ? ?1.0基本線段樹的性質:
? ? ? ? ? ?1.每個節點u表示一個區間[l,r]。若節點u非葉子節點,則u有兩個兒子。設(mid=(l+r)/2)。 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 左兒子為[l,mid],節點編號為u*2。 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 右兒子為[mid+1,r],節點編號為u*2+1。
? ? ? ? ? ? 2.線段樹的深度為O(lgn)
? ? ? ? ? ? 3.線段樹的節點個數為O(n),常數約為4。
? ? ? ? ? ? 4.線段樹上任意一個區間[l,r]都可以被O(logn)個線段覆蓋。
? ? ? ? ? ??
? ? ? ? ? ? ?思考:為何左兒子不為[l,mid+1],為何左兒子的節點編號為u*2。
? ? ? ? ? ? ?思考:如何證明線段樹的節點個數為O(n),且常數為4。
? ? ? ? ? ? ?思考:如何證明線段樹上任意一個區間[l,r]都可以被O(logn)個線段覆蓋。
?
?
倘若我們只需要單點修改,我們只需要找到相應的葉子節點,更改葉子節點并沿路更新。
但倘若我們需要區間修改,我們卻需要引入lazy tag(懶惰標記)。
每一次操作都先下放懶標記,清空標記,再進行相應的操作。
?
如上圖:若將[1,5]全加上3。
需要標記tag[1,5]=3,下一次操作到[1,5]時下放。
tag[1,3]=3,tag[4,5]=3,tag[1,5]=0,sum[1,5]+=3*5;
?
2.基本線段樹的實現:
? ? ? ? 2.1基本線段樹的區間加,詢問區間和:
#include<bits/stdc++.h> #define ll long long using namespace std; const int MAXN=1000005; ll a[MAXN]; ll read() {char c=getchar(); ll f=1,x=0;while (!isdigit(c)) {if (c=='-') f=-1; c=getchar();}while (isdigit(c)) {x=x*10+c-'0'; c=getchar();}return f*x; } struct Segment_tree {struct node{int l,r;ll sum,tag;} tree[MAXN<<2];void up(int x){ tree[x].sum=tree[x<<1].sum+tree[(x<<1)|1].sum; }void down(int x){int num1=(tree[x<<1].r-tree[x<<1].l+1);int num2=(tree[(x<<1)|1].r-tree[(x<<1)|1].l+1);tree[(x<<1)|1].sum+=tree[x].tag*num2; tree[x<<1].tag+=tree[x].tag; tree[(x<<1)|1].tag+=tree[x].tag; tree[x<<1].sum+=tree[x].tag*num1; tree[x].tag=0;}void build(int x,int l,int r,ll *a){tree[x].l=l;tree[x].r=r;if (l==r){tree[x].sum=a[l];tree[x].tag=0;return;}int mid=(l+r)>>1;build(x<<1,l,mid,a);build((x<<1)|1,mid+1,r,a);up(x);}void change(int x,int l,int r,ll y){if (tree[x].l>=l&&tree[x].r<=r){tree[x].tag+=y;tree[x].sum+=y*(tree[x].r-tree[x].l+1);return;}down(x);int mid=(tree[x].l+tree[x].r)>>1;if (r<=mid) change(x<<1,l,r,y);else if (l>mid) change((x<<1)|1,l,r,y);else {change(x<<1,l,mid,y);change((x<<1)|1,mid+1,r,y);}up(x);}ll query(int x,int l,int r){if (tree[x].l>=l&&tree[x].r<=r) return tree[x].sum;down(x);int mid=(tree[x].l+tree[x].r)>>1;if (r<=mid) return query(x<<1,l,r);else if (l>mid) return query((x<<1)|1,l,r);else return query(x<<1,l,mid)+query((x<<1)|1,mid+1,r);}void print(int x){for (int i=1;i<=x;i++) printf("%d %d %d\n",tree[i].l,tree[i].r,tree[i].sum);printf("\n");} } segment_tree; int main() {int n=read(),m=read();for (int i=1;i<=n;i++) a[i]=read();segment_tree.build(1,1,n,a);for (int i=1;i<=m;i++){int c=read();if (c==1){int x=read(),y=read(),z=read();segment_tree.change(1,x,y,z);}else{int x=read(),y=read();printf("%lld\n",segment_tree.query(1,x,y));}}return 0; }?
3.動態開點線段樹
?
動態開點線段樹,實現單點加,區間求和。
#include<bits/stdc++.h> #define ll long long using namespace std; const int MAXN=1000005; const int MAXS=1e9; ll read() {char c=getchar(); ll f=1,x=0;while (!isdigit(c)) {if (c=='-') f=-1; c=getchar();}while (isdigit(c)) {x=x*10+c-'0'; c=getchar();}return f*x; } struct Dynamic_segment_tree {int nodenum=0;struct node{int l,r,leftnode,rightnode;ll sum;} tree[MAXN<<2];void change(int &x,int l,int r,int t,ll y){if (!x){nodenum++;x=nodenum;}tree[x].l=l;tree[x].r=r;tree[x].sum+=y;if (l==r) return;int mid=(tree[x].l+tree[x].r)>>1;if (t<=mid) change(tree[x].leftnode,l,mid,t,y);else change(tree[x].rightnode,mid+1,r,t,y);}ll query(int x,int l,int r){if (!x) return 0;if (tree[x].l>=l&&tree[x].r<=r) return tree[x].sum;int mid=(tree[x].l+tree[x].r)>>1;if (r<=mid) return query(tree[x].leftnode,l,r);else if (l>mid) return query(tree[x].rightnode,l,r);else return query(tree[x].leftnode,l,mid)+query(tree[x].rightnode,mid+1,r);}void print(int x){for (int i=1;i<=x;i++) printf("%d %d %d %d %d\n",tree[i].l,tree[i].r,tree[i].leftnode,tree[i].rightnode,tree[i].sum);printf("\n");} } dynamic_segment_tree; int main() {int n=read(),m=read(),root=0;for (int i=1;i<=m;i++){int c=read(),x=read(),y=read();if (c==1) dynamic_segment_tree.change(root,1,MAXS,x,y);else printf("%d\n",dynamic_segment_tree.query(root,x,y));//dynamic_segment_tree.print(20);}return 0; }4.權值線段樹
權值線段樹,就是每一個線段樹的葉子節點記錄一種值的信息,常用于記錄區間內某權值的出現個數。
這和一般的線段樹差不多,只是節點的意義不同。
例題:求逆序對個數
?
未完待續
總結
以上是生活随笔為你收集整理的数据结构(终极线段树篇)的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 三豆汤的功效与作用、禁忌和食用方法
- 下一篇: 黄蜀葵的功效与作用、禁忌和食用方法
