板子:可持久化資料結構
阿新 • • 發佈:2019-02-05
可持久化線段樹
基本思想
一種犧牲一點空間來達到更多操作的資料結構,似乎可以部分代替平衡樹,並且是個線上的過程。至於更多的細節打算以後去拜讀cls的大作吧,先把基本的弄了來。
思想就是單點修改只需要修改一部分的點,共用一部分的點,然後用來作各種各樣的操作,區間修改也是可以的,但是要稍作改動。
還有就是那個所謂主席樹就是指以下標為時間的權值可持久化線段樹。
至於覺得可持久化線段樹很雞肋的話,倒也不至於,因為雖然感覺很多,但是實現起來還是很簡單的,應該比平衡樹簡單不少了。而且有些時候的確只能用可持久化資料結構做嘛。
程式碼
來兩段核心程式碼吧,其它的基本上和普通線段樹是一樣的。
int copynode(int x)
{
++np;
lc[np]=lc[x],rc[np]=rc[x],cnt[np]=cnt[x];
return np;
}
void Modify(int pre,int &now,int L,int R,int i)
{
now=copynode(pre);
if(L==R)
{
cnt[now]++;
return;
}
int m=(L+R)>>1;
if(i<=m) Modify(lc[pre],lc[now],L,m ,i);
else Modify(rc[pre],rc[now],m+1,R,i);
pushup(now);
}
空間複雜度:2n + mlog2n
時間複雜度:都是log2n
一些延伸操作
一、可以由兩個根同時往下訪問,得到在兩個根下的同一區間。
二、關於帶pushdown的區間修改
用down標記,pushdown的時候為了不破壞原來的結構,新建兩個子結點再常規pushdown,修改的時候只要不是葉子結點就先pushdown下去
我來解釋一下:如果後pushdown的話,那麼和新結點就沒什麼關係了,新結點又不會繼承down的這個值,所以看起來就像是繼承了前面的前面那種情況,當然查詢pushdown是可以放在後面的。
至於空間分析:根據每一層都只會訪問兩個結點(來自《統計的力量》),那麼就是2*log2n, 然後每次都可能因為pushdown多做兩個子樹,再加上本身結點的賦值,那麼就是 6 *log2n *m(修改+查詢)這麼多咯。
三、樹上的路徑尋找問題
對於不修改的樹,可以在父親結點的基礎上建立可持久化線段樹(弄一個DFS序出來(最簡單的序即可,你非要搞樹剖我也沒辦法啊)),就可以搞了。
程式碼
貼一下核心程式碼
void pushdown(int now,int L,int R)
{
if(down[now])
{
lc[now]=copynode(lc[now]);
rc[now]=copynode(rc[now]);
int m=(L+R)>>1;
down[lc[now]]+=down[now];
sum[lc[now]]+=1ll*down[now]*(m-L+1);
down[rc[now]]+=down[now];
sum[rc[now]]+=1ll*down[now]*(R-m);
down[now]=0;
}
}
void modify(int pre,int &now,int L,int R,int i,int j,int t)
{
if(L!=R)pushdown(pre,L,R);
now=copynode(pre);
if(i<=L && R<=j)
{
sum[now]+=1ll*(R-L+1)*t;
down[now]+=t;
return;
}
int m=(L+R)>>1;
if(i<=m) modify(lc[pre],lc[now],L,m,i,j,t);
if(j>m) modify(rc[pre],rc[now],m+1,R,i,j,t);
pushup(now);
}
LL Qsum(int now,int L,int R,int i,int j)
{
if(i<=L && R<=j)
return sum[now];
pushdown(now,L,R);
LL ret=0;
int m=(L+R)>>1;
if(i<=m)ret+=Qsum(lc[now],L,m,i,j);
if(j>m)ret+=Qsum(rc[now],m+1,R,i,j);
return ret;
}
可持久化資料結構補充
- 可持久化陣列:丫的就是可持久化線段樹啊
- 可持久化treap:每次把需要修改的結點Newnode出來