hit軟構部落格1--git工具使用的學習
0.什麼是線段樹
線段樹有著比分塊小的時間複雜度,也有著比樹狀陣列大的通用性,有著比平衡樹更小的常數和程式碼難度,實在是一種非常牛逼的東西。
1.線段樹入門
我們要了解線段樹,首先得明白它長什麼樣。
此處放一張圖:
如圖,一個節點代表一個區間,從中點切開這個區間,就成為了左右區間(不一定嚴謹,聽著吧)。
由於每切一次,區間都減少一半,因此樹高為\(\log n\)級別。
葉節點的值直接由序列得出,其它節點有左右節點合併得出。
為方便,將\(p\)的左右節點分別設為\(p\times2\)和\(p\times2+1\)(可以證明這樣節點不會重複,只是犧牲了一點空間)。
詞窮了,直接開始上程式碼吧。
//以下為加減,求和操作
void pushup(int p){
val[p]=val[p<<1]+val[p<<1|1];
}
void build(int p,int l,int r){//建樹
if(l==r){
val[p]=v[l];//v[l]為序列第l項
}
int mid=(l+r>>1);
build(p<<1,l,mid);build(p<<1|1,mid+1,r);
pushup(p);//將左右節點的資訊合併
}
學會了建樹,接下來就應該是操作了。
操作1:單點查詢
簡單到不知道如何解釋了,直接上程式碼。
int query(int p,int l,int r,int pos){//pos為查詢位置 if(l==r)return val[p]; int mid=(l+r>>1); if(pos<=mid)return query(p<<1,l,mid,pos); else return query(p<<1|1,mid+1,r,pos); }
操作2:單點修改
自上而下到葉節點進行修改,再自下而上更新
void modify(int p,int l,int r,int pos,int x){
if(l==r){
val[p]+=x;
return;
}
int mid=(l+r>>1);
if(pos<=mid)modify(p<<1,l,mid,pos);
else modify(p<<1,mid+1,r,pos);
}
以上操作暴力都可以做到,現在就要介紹暴力做不到的:區間操作。
首先上簡單點的:區間查詢。
我們想一想,維護非葉節點有什麼用?進行區間操作。
先上程式碼:
void query(int p,int l,int r,int L,int R){
if(L<=l&&r<=R)return val[p];//當前區間被完全包含,直接返回
int mid=(l+r>>1),ans=0;
if(L<=mid)ans+=query(p<<1,l,mid,L,R);//左兒子與詢問區間有交集
if(R>mid)ans+=query(p<<1|1,mid+1,r,L,R);//右兒子與詢問區間有交集
}
易證,資訊肯定是不重不漏的。
下面進行時間複雜度證明:
只有包含端點的區間才能一直遞迴下去。
未包含端點有兩種情況:完全在詢問區間內,完全在詢問區間外,這些都是直接返回。
而端點只有兩個,樹高為\(\log n\)級別,因此查詢複雜度也為\(\log n\)
接下來是區間修改。
詢問可以不到葉子,因為只要獲取資訊。但修改不行,因為修改要將資訊更新。
好像思路進入了僵局。
但線段樹有一個強大的功能:懶標記。
有一個標記,代表此節點已被更新,但子節點未被更新。
如果區間被修改區間完全包含,打一個標記,並更新它的資訊。
如果以後需要對此區間的兒子進行操作,就將標記下傳,更新兒子節點的資訊,將兒子節點打上標記,並將此節點的標記去除。
標記下傳複雜度理論為\(O(1)\),因此只是常數大了那麼一點(不是億點)。
void pushdown(int p,int l,int r){
if(tag[p]){
int mid=(l+r>>1);
val[p<<1]+=(mid-l+1)*tag[p];
val[p<<1|1]+=(r-mid)*tag[p];
tag[p<<1]+=tag[p];tag[p<<1|1]+=tag[p];
tag[p]=0;
}
}
void modify(int p,int l,int r,int L,int R,int x){
if(L<=l&&r<=R){
val[p]+=(r-l+1)*x;tag[p]+=x;return;
}
pushdown(p,l,r);int mid=(l+r>>1);
if(L<=mid)modify(lc,l,mid,L,R,x);
if(R>mid)modify(rc,mid+1,r,L,R,x);
}
P3372程式碼如下:
#include<bits/stdc++.h>
using namespace std;
int n,m,a[100001];
struct tree{
int l,r;
long long sum,add;
}tree[400001];
void label(int p){
if(tree[p].add){
tree[p*2].sum+=(tree[p*2].r-tree[p*2].l+1)*tree[p].add;
tree[p*2+1].sum+=(tree[p*2+1].r-tree[p*2+1].l+1)*tree[p].add;
tree[p*2].add+=tree[p].add;
tree[p*2+1].add+=tree[p].add;
tree[p].add=0;
}
}
void bulid(int l,int r,int p){
tree[p].l=l,tree[p].r=r;
if(l==r){
tree[p].sum=a[l];
return;
}
int mid=(l+r)/2;
bulid(l,mid,p*2);
bulid(mid+1,r,p*2+1);
tree[p].sum=tree[p*2].sum+tree[p*2+1].sum;
}
void change(int l,int r,int p,int num){
if(tree[p].l>=l&&tree[p].r<=r){
tree[p].sum+=1ll*num*(tree[p].r-tree[p].l+1);
tree[p].add+=num;
return;
}
label(p);
int mid=(tree[p].l+tree[p].r)/2;
if(l<=mid)change(l,r,p*2,num);
if(r>mid)change(l,r,p*2+1,num);
tree[p].sum=tree[p*2].sum+tree[p*2+1].sum;
}
long long ask(int l,int r,int p){
long long ans=0;
if(tree[p].l>=l&&tree[p].r<=r){
return tree[p].sum;
}
label(p);
int mid=(tree[p].l+tree[p].r)/2;
if(l<=mid)ans+=ask(l,r,p*2);
if(r>mid)ans+=ask(l,r,p*2+1);
return ans;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d",a+i);
}
bulid(1,n,1);
while(m--){
int op,l,r;
scanf("%d%d%d",&op,&l,&r);
if(op==1){
int x;
scanf("%d",&x);
change(l,r,1,x);
}
else printf("%lld\n",ask(l,r,1));
}
return 0;
}
其它操作例如區間最值,其實是大同小異的。