P1600 天天愛跑步 桶 LCA 樹上差分

2022-04-30 21:27:14 字數 4559 閱讀 5006

小c同學認為跑步非常有趣,於是決定製作一款叫做《天天愛跑步》的遊戲。《天天愛跑步》是乙個養成類遊戲,需要玩家每天按時上線,完成打卡任務。

這個遊戲的地圖可以看作一一棵包含 nn個結點和 n-1n−1條邊的樹, 每條邊連線兩個結點,且任意兩個結點存在一條路徑互相可達。樹上結點編號為從11到nn的連續正整數。

現在有mm個玩家,第ii個玩家的起點為 s_is**i,終點為 t_it**i 。每天打卡任務開始時,所有玩家在第00秒同時從自己的起點出發, 以每秒跑一條邊的速度, 不間斷地沿著最短路徑向著自己的終點跑去, 跑到終點後該玩家就算完成了打卡任務。 (由於地圖是一棵樹, 所以每個人的路徑是唯一的)

小c想知道遊戲的活躍度, 所以在每個結點上都放置了乙個觀察員。 在結點jj的觀察員會選擇在第w_jw**j秒觀察玩家, 乙個玩家能被這個觀察員觀察到當且僅當該玩家在第w_jw**j秒也理到達了結點 jj 。 小c想知道每個觀察員會觀察到多少人?

注意: 我們認為乙個玩家到達自己的終點後該玩家就會結束遊戲, 他不能等待一 段時間後再被觀察員觀察到。 即對於把結點jj作為終點的玩家: 若他在第w_jw**j秒前到達終點,則在結點jj的觀察員不能觀察到該玩家;若他正好在第w_jw**j秒到達終點,則在結點jj的觀察員可以觀察到這個玩家。

可能是目前為止我做過的最難的題了。。。(話說這題為什麼不是黑的)

再吐槽一句,考場上誰想得出這種神仙演算法啊?!勉強拿個80暴力分都不容易啊!

在講這題前,首先要引入乙個船新的概念:全域性桶

還有一些老東西:樹上差分,lca。

全域性桶,顧名思義,也是種桶,但是其維護的不是物件對應的下標,而是類似用桶來裝權值(怎麼說著那麼奇怪)。精確的說,它維護的是物件產生的值出現的次數,而不是物件出現的次數。有點說不清楚,解釋一下,比如這題,我們的全域性桶將要維護的是每乙個玩家的起點、終點對其跑步的路徑所造成的影響,這個影響也就是起點、終點、路徑上的觀察員共同產生的乙個值,下面會詳細講。我們會看到,它不維護樹上某乙個點的權值,而是作為總體的計數陣列而存在。

即最近公共祖先,不必多言,看名字就知道啥意思。

為了快速計算子樹和,或維護子樹的一些滿足區間可減性(即滿足該資訊的字首和可以相減,其可作為字首和的逆運算)的資訊時,樹上差分可以很好勝任。模擬序列上的差分,假設我們有一棵樹\(t\),要在一條\(s\sim t\)的路徑上對每個點的權值加\(k\),我們可以對差分陣列執行以下操作:在\(s\)加\(k\),在\(t\)加\(k\),在\(lca(s,t)\)減\(k\),在\(father(lca(s,t))\)減\(k\),那麼以\(lca(s,t)\)為根的子樹字首和(從葉子節點向根節點做字首和,根節點算在內)就是原來的子樹和。(樹剖或lct可取而代之)

解釋完概念之後,我們來著手這道題的解決。

分析題目,容易得出每個玩家其實就是對\(s\sim t\)的路徑上的點依次增加了\(1\sim len(s,t)\)的權值。故有暴力演算法,對每個運動員,我們處理出他跑的路徑對樹的貢獻,最後暴力dfs整棵樹統計答案,複雜度\(o(nm)\)。優化的好的話就能得到80分了。。。

似乎和樹上差分沒什麼關係?

我們不妨轉化一下思維,逆向看問題。

對於樹上每個節點,它都有可能作為某一對起點和終點的lca,假設某個在\(s\sim t\)上的觀察員為\(x\)。而\(x\)對最終答案產生貢獻僅當\(deep[x]+w[x]=deep[s]\)或\(deep[x]+deep[s]-deep[lca(s,t)]*2=w[x]\)時。這是比較顯而易見的表達方式,然後我們將其移項,使得其呈現規律性:\(deep[s]=deep[x]+w[x]\),\(deep[s]-deep[lca(s,t)]*2=w[x]-deep[x]\)。好的!現在關於\(x\)的式子都在等號左邊了。這就方便了我們來維護這些等式,但這也是難點所在。

根據以上分析,我們就得分\(s\sim lca(s,t)\),\(lca(s,t) \sim t\)兩條路徑討論了。

我們先考察稍微簡單一點的\(s\sim lca(s,t)\)這條路徑。

不難想到,對於乙個玩家,它的起點\(s\)會給\(s\sim lca(s,t)\)上的所有點\(x\)造成\(deep[s]\)的貢獻,只有當\(x\)的貢獻\(deep[x]+w[x]\)與其相等時,\(x\)才能觀察到該玩家,該點答案\(+1\)。換句話說,就是對該路徑上所有點加上\(deep[x]\)。你想到了什麼?沒錯,樹上差分,這是很自然的乙個推導過程。假設這個差分陣列為陣列\(c\),此時我們對\(c[s]\)加\(deep[s]\),對\(c[lca(s,t)]\)減\(deep[x]\)。

對於\(s\sim lca(s,t)\)這條路徑呢,我們就給\(c[lca(s,t)]\)減去\(deep[s]-deep[lca(s,t)]*2\),給\(c[t]\)加上\(deep[s]-deep[lca(s,t)]*2\)。等等,lca怎麼算重了???沒事,你只減一條路上lca就行,你會發現在lca這裡,兩個等式是等價的。複雜度因人而異,但肯定不會超過\(o(nm)\)啦。

這樣做,你需要線段樹合併,或者別的資料結構來維護這個差分陣列。你愉快的發現,你t了。

我們還沒拿出來全域性桶呢,不得不說,這個辦法太秀了,也比較抽象。我們再逆向一下思維,考慮所有點作為lca時的情況

說白了其實就是上面那種方法逆過來搞,可以實現一種先處理所有玩家最後統計答案的演算法,複雜度可以達到\(o((n+m)log(n+m))\)。

這個全域性桶,它的好處就是去掉了許多無用的條件,只保留最少的我們所需要的條件。

建立全域性桶\(bucket\),對每種型別的貢獻(比如\(deep[x]+w[x]\))進行計數。

對樹上每乙個節點開4個多重集(比如vector),將所有經過該節點的玩家的起點放進乙個集合,終點放進乙個集合,將該節點作為一條路徑的終點放進乙個集合,將該節點作為一條路徑的起點放進乙個集合。首先起點和終點直接的路徑時一定的,且一定會經過它們的lca。那麼對於某個節點\(x\),第乙個集合也就對應著所有經過\(x\)的路徑,或者說以\(x\)為lca的起點的貢獻,第二個集合對應著所有以\(x\)為lca的終點的貢獻。

最後,dfs整顆樹,計算樹上字首和。具體來說,對於\(s\sim lca(s,t)\),就是遞迴進入在這條路徑上的點\(x\)時,對於第乙個集合中的點集\(i_1\),給\(bucket[deep[i_1]]-1\),對於第二個集合,也給\(bucket[deep[i_2]]-1\),對於第三個集合,給\(bucket[deep[i_3]]+1\),對第四個集合,給\(bucket[deep[i_4]]+1\)。等我們又回溯到\(x\)時,它的子樹已經對桶完成更新,意即所有能被\(x\)觀察到的玩家都被算進了\(bucket[deep[x]+w[x]]\),因此此時\(bucket[deep[x]+w[x]]\)前後的差值就是\(x\)點對答案的貢獻。

發現又加重了,我們最後減掉就行。

注意,對於\(lca(s,t)\sim t\)這條路徑,統計的時候桶下標會溢位,我們要離散化或者平移座標處理。

回顧樹上差分方法,你會發現二者其實在做同一件事情,而後者真是驚豔到我了。

#include#include#include#include#include#include#include#include#include#include#define n 600010

#define m 300010

using namespace std;

inline int read()

while(c>='0'&&c<='9')

return x*f;

}int f[31][n],n,m,a[n+m],c[n],dep[n],t,sum[n],ans[n];

vectorv1[m],v2[m],v3[m];

struct recg[n];

int head[n],tot;

struct nodes[m];

inline void add(int x,int y)

inline void init()

} }inline int lca(int x,int y)

inline void dfs1(int x,int fa)

c[dep[x]+m]+=sum[x];

ans[x]+=c[dep[x]+a[x]+m]-cnt;

for(int i=0;ic[v1[x][i]+m]--;

}inline void dfs2(int x,int fa)

for(int i=0;ic[v3[x][i]+m]++;

ans[x]+=c[a[x]-dep[x]+m]-cnt;

for(int i=0;ic[v2[x][i]+m]--;

}int main()

init();

for(int i=1;i<=n;++i) a[i]=read();

for(int i=1;i<=m;++i)

dfs1(1,-1);

dfs2(1,-1);

for(int i=1;i<=m;++i)

if(dep[s[i].s]==dep[s[i].lca]+a[s[i].lca]) ans[s[i].lca]--;

for(int i=1;i<=n;++i)

printf("%d ",ans[i]);

return 0;

}

P1600 天天愛跑步 線段樹合併 lca

天天愛跑步 有點毒瘤的題目 我們觀察一下性質 在路徑 u,v 上 如果這個路徑 對某個點j 有貢獻j肯定得在路徑 u,v上 也就是說 如果當前遍歷的點在 lca u,v 上方的話 那麼 u,v路徑是肯定沒有貢獻的 因此到達lca時我們要把貢獻去掉 在說怎麼算貢獻 設u為起始點 v為終止點 如果u對於...

洛谷P1600 天天愛跑步 差分 LCA 桶

題目鏈結 一步一步的來考慮 25 直接 o nm 的暴力 鏈的情況 維護兩個差分陣列,分別表示從左向右和從右向左的貢獻,s i 1 統計每個點的子樹內有多少起點即可 t i 1 同樣還是差分的思想,由於每個點 能對其產生的點的深度是相同的 假設為 x 那麼訪問該點時記錄下 dep x 的數量,將結束...

洛谷 P1600 天天愛跑步(LCA 亂搞)

傳送門 我們把每一條路徑拆成 u lca 和 lca v 的路徑 先考慮 u lca 如果這條路徑會對路徑上的某乙個點產生貢獻,那麼滿足 dep u dep x w x dep u dep x w x 注意到 dep x w x 是乙個定值,所以我們只要去找它的子樹裡有多少個點的 dep 等於 de...