【瞎扯】树上差分的基本思路
? ? ? ? ?數(shù)據(jù)結(jié)構(gòu)題中解法千變?nèi)f化,但分析最近幾年的趨勢來看,有一種比較重要的思想->樹上差分。(會樹剖的大神不要嘲笑,雖然很多時候樹剖都能很好解決QwQ)。至少,樹上差分熟練的話還是可以解決很多問題的。這里就先分析兩種基本的差分思路。
1.找被所有路徑共同覆蓋的邊。
? ? ? ? ?可能這樣講不是很詳細,那就看一道例題【Noip2015】運輸計劃(【Bzoj4326】)。大意是有許多條運輸路徑,讓你在把一條邊的用時不計的情況下找到最大路徑的最短用時。看到這題很明顯想到二分答案,而對于每一個答案,我們要記錄超過此答案的計劃。之后我們該怎么辦呢,如果有一條邊被所有計劃共同覆蓋,且去掉它(用時降為0)后能使得其中超答案時間最多的邊都不超時,那便是可行答案。而我們就要來分析一下如何找這條邊。
? ?首先我們除了一般的grand,depth等數(shù)組以外,多開兩個數(shù)組:tmp和prev。tmp用來記錄點的出現(xiàn)次數(shù)(具體點說實際上記錄的是點到其父親的邊的出現(xiàn)次數(shù)),prev記錄每個點到其父親的那條邊(這里根據(jù)題目記的權(quán)值,有必要時也可以記編號)。對于一條起點s,終點t的路徑。我們這樣處理:tmp[s]++,tmp[t]++,tmp[LCA(s,t)]-=2。(記住:最后要從所有葉結(jié)點把權(quán)值向上累加。)以一次操作為例,我們來看看效果(可以畫一張圖)。首先tmp[s]++,一直推上去到根,這時候s到root的路徑訪問次數(shù)都+1,tmp[t]++后,t到lca路徑加了1,s到lca路徑加了1,而lca到根的路徑加了2。這時,我們只需要tmp[LCA(s,t)]-=2,推到根,就能把那些多余的路徑減掉,達到想要的目的。而這是一次操作,對于很多次操作的話,我們只需要維護tmp,而不必每次更新到根,維護好tmp最后Dfs一遍即可。這時如果tmp[i]==次數(shù)的話,說明i到其父親的邊是被所有路徑覆蓋的。
? ? ? ? ?光是看起來可能很麻煩,建議畫一張圖模擬一個簡單的過程。代碼實現(xiàn)的話可以參考這個運輸計劃中的二分check過程...丟上來。(如果像這樣多次找的話注意tmp的重置。)(kth可以暫時不用管,這只是這道題中維護的點的編號。)
bool check(int x){ int cnt=0,dist=0; memset(tmp,0,sizeof(tmp)); for(int i=1;i<=m;i++){ if(Q[i].Dis > x){ tmp[Q[i].s]++;tmp[Q[i].t]++; tmp[Q[i].lca] -= 2; dist=chkmax(dist,Q[i].Dis-x); cnt++; } } if(!cnt) return true; for(int i=n;i>1;i--) tmp[grand[kth[i]][0]] += tmp[kth[i]]; for(int i=2;i<=n;i++) if(tmp[i]==cnt && prev[i]>=dist) return true; return false; }
2.將路徑上的所有點權(quán)值加一,求最后點的權(quán)值。
? ? ? ? ? ?乍一看和上一個操作是差不多的,但實際上還是有一些不同,如果自己在畫圖中發(fā)現(xiàn)問題就能體會到這種差異。
? ? ? ??而對于這種操作,我們該怎么實現(xiàn)呢?首先還是通過例題了解具體操作,這里推薦【JLOI2014】松鼠的新家(【Bzoj3631】)大意是有一些路徑,每次經(jīng)過時要將路徑上的所有點權(quán)值加上1。對于這種操作,我們?nèi)孕枰粋€tmp數(shù)組。這里的tmp記錄的就是點被訪問的次數(shù)(也可以說是累加的權(quán)值)。這個操作的具體實現(xiàn)和上一個思想類似(都是差分)實現(xiàn)的話也只有少許不同。此操作中我們這樣維護:每次經(jīng)過一條邊,(如從u到v)我們讓tmp[u]++,tmp[v]++,tmp[LCA(u,v)]--,tmp[grand[LCA(u,v)][0]]--。(最后要把tmp推上去)以一次添加為例想象一下,首先u到根的路徑上tmp都+1,此時u到根間結(jié)點tmp都為1,之后v到根路徑上tmp+1,此時u到LCA前一個,v到LCA前一個點的tmp都+1,而LCA到根的所有點都+2,然后從tmp[LCA]--,更新上去,此時u-v路上所有tmp都+1,已經(jīng)達到目的。而多余的是什么部分呢,也就是LCA的上一個結(jié)點(grand[LCA][0])到根的這一段都多加了1,所以tmp[grand[LCA][0]]--,更新上去,也就完成了。實際操作時也不需要每次更新都推上去,只要把四個tmp維護好,最后Dfs走一邊就更新完了。
如果理解不了的話建議還是畫一張圖(...可以理解的...吧...?)反正體會其中的思想,自己多模擬過程。(這個
...)不知道是不是表達能力的問題(自以為還是表達清楚了的)...實在看不懂的話...我也很難過啊。
代碼也扔一個(一小段)
void pushdown(int x) { for(int i=head[x];i;i=next[i]) { if (to[i]==grand[x][0]) continue; pushdown(to[i]); tmp[x]+=tmp[to[i]]; } } int main(){ n = read(); for(int i=1;i<=n;i++) a[i] = read(); for(int i=1;i<n;i++){ x = read();y = read(); Add(x,y);Add(y,x); } Dfs(a[1]); for(int i=1;i<n;i++){ int u = a[i],v = a[i+1]; tmp[u]++;tmp[v]++; tmp[Lca(u,v)]--; tmp[grand[Lca(u,v)][0]]--; } pushdown(a[1]); for(int i=2;i<=n;i++) tmp[a[i]]--; for(int i=1;i<=n;i++) printf("%d\n",tmp[i]); return 0; }
反正注意兩種操作的相同與不同之處,可以多刷一些題加深理解,這篇文也許(hhh也許)還會更新,如果
有新的差分思路或者好題啥的,還會update的。
看不懂的話可以加qq騷擾我辣,雖然加了也不一定講得清...反正反正反正qwq...要知道我講得這么不清楚是
愛得深沉的表現(xiàn)知道嗎??!
hhhhhhhhhhhhhh又精神分裂了,反正記住是因為想講清(導(dǎo)致講得不清...?)hhhhh。就這樣,講得爛不
要噴我,不接受。hhhhhhh。
總結(jié)
以上是生活随笔為你收集整理的【瞎扯】树上差分的基本思路的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: pd.concat数据拼接
- 下一篇: 什么是 PendSV