[OI學習筆記]線段樹模板
阿新 • • 發佈:2019-01-31
樹狀數組 lazy 重復 優化 查詢 amp cor 葉子 scan
背景
今天課上講了樹狀數組和線段樹,有是一臉懵逼。。。
於是網上有惡補了一下,
看了幾個小時blog終於懂了。。。
由於很難解釋,就只貼兩個模板代碼(一個單點修改+單點和區間查詢,一個區間修改+單點和區間查詢)
幾點註意事項
1.首先跑區間更新的程序,一般對於每一個點要維護一個lazy變量來臨時存儲它將要向下更新的數值,然後對於要查尋的點,直接向下更新,這樣偷懶更省時間。(具體實現過程見代碼)。
2.空間要開到4*N(N為元素個數)(好像可以優化成2*N,但是本蒟蒻不知道怎麽優化)
3.打線段樹坑點還是很多的,下面列舉一下我所犯過的錯誤:
1st.睿智地把mid=(tree[k].r+tree[k].l)/2和(tree[k*2].r-tree[k*2].l+1)搞混(全國可能就我會這樣了。。。)
2nd.下放lazy更新子節點的權時,要用父節點的lazy更新,並且區間長度要+1.
3rd.下放完lazy父節點的lazy值要標位0!!防止重復下放!
大概就是這些,,,下面直接上代碼
代碼
線段樹單點修改兩種查詢:
#include<cstdio> #include<iostream> using namespace std; const int MAXN=10010; int n;//n個元素,m個操作 struct node{ int l,r,v; }tree[4*MAXN+1];//其實最少開到4*MAXN就行 void build(int l,int r,int k){//當前節點存儲的線段的左右端點&這個節點的編號 tree[k].l=l;tree[k].r=r; if(l==r){ scanf("%d",&tree[k].v);//葉子節點的值可在這裏讀入! return; } int mid=(l+r)/2; build(l,mid,k*2); build(mid+1,r,k*2+1); tree[k].v=tree[k*2].v+tree[k*2+1].v; } //單點查詢(葉子節點) int askpoint(int x,int k){//目標點在原序列中的下標 k為當前樹中的位置下標 if(tree[k].l==tree[k].r){ return tree[k].v; } int mid=(tree[k].r+tree[k].l)/2;//錯點1 // if(x<=mid)return askpoint(x,k*2); else return askpoint(x,k*2+1); } //單點修改 void changepoint(int x,int num,int k){//要在序列中的第x個數中加上num k為當前樹中的下標 if(tree[k].l==tree[k].r){ tree[k].v+=num; return; } int mid=(tree[k].r+tree[k].l)/2;//錯點1 // if(x<=mid)changepoint(x,num,k*2); else changepoint(x,num,k*2+1); tree[k].v=tree[k*2].v+tree[k*2+1].v;//重新整合區間和 } //區間查詢 int sum=0; void asksec(int tl,int tr,int l,int r,int k){ if(l>=tl&&r<=tr){ sum+=tree[k].v; return; } int mid=(tree[k].l=tree[k].r)/2; if(tl<=mid)asksec(tl,tr,l,mid,k*2); if(tr>mid)asksec(tl,tr,mid+1,r,k*2+1);//錯點2 不是else// } int main(){ cout<<"點數"<<endl;//這個程序裏不用問題數 scanf("%d",&n); cout<<"build tree"<<endl; build(1,n,1); cout<<"change point"<<endl; int x,num; scanf("%d%d",&x,&num); changepoint(x,num,1); cout<<"ask point"<<endl; scanf("%d",&x); printf("%d\n",askpoint(x,1)); cout<<"ask section"<<endl; int tl,tr; scanf("%d%d",&tl,&tr); asksec(tl,tr,1,n,1); printf("%d\n",sum); return 0; }
線段樹區間修改兩種查詢:
#include<cstdio> #include<iostream> using namespace std; const int MAXN=10010; int n;//n個元素,m個操作 struct node{ int l,r,v,lazy; }tree[4*MAXN+1];//其實最少開到4*MAXN就行 void build(int l,int r,int k){//當前節點存儲的線段的左右端點&這個節點的編號 tree[k].l=l;tree[k].r=r; if(l==r){ scanf("%d",&tree[k].v);//葉子節點的值可在這裏讀入! return; } int mid=(l+r)/2; build(l,mid,k*2); build(mid+1,r,k*2+1); tree[k].v=tree[k*2].v+tree[k*2+1].v; } //下放lazy標記函數 void putdown(int k){ tree[k*2].lazy+=tree[k].lazy; tree[k*2+1].lazy+=tree[k].lazy; tree[k*2].v+=tree[k].lazy*(tree[k*2].r-tree[k*2].l+1);//坑點:1.要用父節點k的lazy 2.區間長度記得要+1// tree[k*2+1].v+=tree[k].lazy*(tree[k*2+1].r-tree[k*2+1].l+1);//坑點:同上// tree[k].lazy=0;//坑點:父節點的lazy要清0!!!!!// } //單點查詢(葉子節點) int askpoint(int x,int k){ if(tree[k].l==tree[k].r){ return tree[k].v; } if(tree[k].lazy)putdown(k);//防止重復下放 int mid=(tree[k].l+tree[k].r)/2; if(x<=mid)return askpoint(x,k*2); else return askpoint(x,k*2+1); } //區間修改 void changesection(int tl,int tr,int num,int k){ if(tl<=tree[k].l&&tr>=tree[k].r){//對查詢有用 tree[k].v+=num*(tree[k].r-tree[k].l+1);//坑點// tree[k].lazy+=num; return; } if(tree[k].lazy)putdown(k);//防止重復下放 int mid=(tree[k].r+tree[k].l)/2;//錯點 // if(tl<=mid)changesection(tl,tr,num,k*2); if(tr>mid)changesection(tl,tr,num,k*2+1); tree[k].v=tree[k*2].v+tree[k*2+1].v; } //區間查詢 int sum=0; void asksec(int tl,int tr,int l,int r,int k){ if(l>=tl&&r<=tr){ sum+=tree[k].v; return; } if(tree[k].lazy)putdown(k);//防止重復下放 int mid=(tree[k].r+tree[k].l)/2; if(tl<=mid)asksec(tl,tr,l,mid,k*2); if(tr>mid)asksec(tl,tr,mid+1,r,k*2+1); } int main(){ freopen("in.in","r",stdin); cout<<"點數"<<endl; scanf("%d",&n); cout<<"build tree"<<endl; build(1,n,1); cout<<"change section"<<endl; int x,y,num; scanf("%d%d%d",&x,&y,&num); changesection(x,y,num,1); cout<<"ask point"<<endl; scanf("%d",&x); printf("%d\n",askpoint(x,1)); cout<<"ask section"<<endl; int tl,tr; scanf("%d%d",&tl,&tr); asksec(tl,tr,1,n,1); printf("%d\n",sum); return 0; }
[OI學習筆記]線段樹模板