動態開點線段樹
閱讀本篇請先學習線段樹。
動態開點線段樹是一類特殊的線段樹,與普通的線段樹不同的是,每一個節點的左右兒子不是該點編號的兩倍和兩倍加一,而是現加出來的。
一般有兩種:為了節約空間,我們會不一次性建好樹,而是需要時再建。
還有一種,就是運用主席樹(可持久化線段樹)的時候。
我們先說節約空間用的動態開點線段樹。
我們用lson[u]記錄u的左兒子,rson[u]記錄u的右兒子(博主不用define於是用ll[u]和rr[u]),l2,r2代表了這次修改所覆蓋的范圍,其他就不贅述了,以下是建樹的代碼:
void build(long long u,long long l1,long long r1,long long l2,long long r2) { l[u]=l1; r[u]=r1; if (l1==r1||l1>=l2&&r1<=r2)//當已經是一個點的時候或被包含的時候就直接返回了 return; if (l2<=(l1+r1)/2)//需建左區間 { ll[u]=++cnt; build(ll[u],l1,(l1+r1)/2,l2,r2); } if (r2>(l1+r1)/2)//需建右區間 { rr[u]=++cnt; build(rr[u],(l1+r1)/2+1,r1,l2,r2); } }
完整代碼如下:
void xiafang(long long u) { z[ll[u]]=c[u]*(r[ll[u]]-l[ll[u]]+1); c[ll[u]]+=c[u]; z[rr[u]]=c[u]*(r[rr[u]]-l[rr[u]]+1); c[rr[u]]+=c[u]; c[u]=0; } void build(long long u,long long l1,long long r1,long long l2,long long r2) { l[u]=l1; r[u]=r1; if (l1==r1) return; if (l2<=(l1+r1)/2) { ll[u]=++cnt; build(ll[u],l1,(l1+r1)/2,l2,r2); } if (r2>(l1+r1)/2) { rr[u]=++cnt; build(rr[u],(l1+r1)/2+1,r1,l2,r2); } } void jia(long long u,long long l1,long long r1,long long k) { if (l[u]>r1||r[u]<l1) return; if (l[u]>=l1&&r[u]<=r1) { z[u]+=k*(r[u]-l[u]+1); c[u]+=k; return; } if (!ll[u]) { ll[u]=++cnt; build(ll[u],l[u],(l[u]+r[u])/2,l1,r1);//需要建樹時 } if (!rr[u]) { rr[u]=++cnt; build(rr[u],(l[u]+r[u])/2+1,r[u],l1,r1); } if (c[u]) xiafang(u); jia(ll[u],l1,r1,k); jia(rr[u],l1,r1,k); z[u]=z[ll[u]]+z[rr[u]]; } long long qui(long long u,long long l1,long long r1) { if(!u) return 0;//就是沒有這個點,也就是未賦值,返回0 if (l[u]>r1||r[u]<l1) return 0; if (l[u]>=l1&&r[u]<=r1) return z[u]; if (c[u]) xiafang(u); return (qui(ll[u],l1,r1)+qui(rr[u],l1,r1)); }
還有一種是可持久化線段樹中的運用
這個可以直接看博主寫的可持久化線段樹的博文,博主將這段復制到那邊去了一份,就不用來回看了……
其實很簡單,只要將上文中的新建節點變成在重新賦值時以模式樹進行修改就行了,代碼如下:
void jia(long long mo,long long u,long long x,long long k)//mo:模式版本的代表該區間的節點,u:我們要構造的節點,x:區間位置,k:要修改成的值 { l[u]=l[mo]; r[u]=r[mo]; if (l[u]==r[u]) { z[u]=k; return; }//這個和build很像 if (x<=(l[u]+r[u])/2)//x在左兒子 { rr[u]=rr[mo];//右邊和模式版本一樣 cnt++; ll[u]=cnt; jia(ll[mo],ll[u],x,k); z[u]=z[ll[u]]+z[rr[u]]; } else { ll[u]=ll[mo];//左邊就和模式版本一樣 cnt++; rr[u]=cnt; jia(rr[mo],rr[u],x,k); z[u]=z[ll[u]]+z[rr[u]];//x在右兒子 } }
其實用一個計數器記錄一下開到了哪個節點,然后需要再開點時增加,就算是動態開點的重要之處,注意不滿足lson[u]=u*2,rson[u]=u*2+1,因此需要數組記錄。