吉姆 102394 H

zzafanti發表於2024-06-13

描述

一個無向連通圖,每個點 \(u\) 有點權 \(w_u\),處在 \(u\) 點時可以花費 \(w_u\) 的時間去任何一個距離 \(u\) 最短路徑邊數不超過 \(f_u\) 的點。

現在,從 \(1\) 號點出發,對於每個 \(1\le k\le n\),求出 \(1\)\(k\) 需要的最小時間。

  • \(n\leq 200000\)
  • \(m\leq n+50\)

解決方案

首先解決,對於 \(n\) 個點 \(m\) 條邊且只有點權的圖,怎麼 \(O(n\log n+m)\) 求單源最短路。

考慮 Dijkstra 的時候,以 \(dis_i+w_i\) 為關鍵字建立小根堆,其中 \(dis_i\) 是起點到 \(i\) 的最短路,每次取出堆頂節點 \(u\),掃描出邊 \((u,v)\),如果 \(dis_v\) 還沒有被求出來,就直接用 \(dis_u+w_u\) 更新 \(dis_v\)

這樣做的好處是每個點只會被入堆一次。

回到本題,距 \(u\) 距離不超過 \(f_u\) 的點數是 \(O(n)\) 的,所以跑最短路的總邊數是 \(O(n^2)\) 的,直接用上面的演算法不可接受。

由於每個點只要入堆一次,所以對於當前堆頂節點 \(u\),找出所有還未求出 \(dis\) 的距其不超過 \(f_u\) 的點 \(v\) 更新就可以保證更新的次數為 \(O(n)\) 次。

考慮輸入的圖是一棵樹怎麼做。

對於每個點,記錄點分樹上子其樹內的點到它在原樹上的距離。查詢一個點 \(u\) 的出邊時,在點分樹上找到 \(u\) 自己和它的所有祖先構成的集合 \(S\),對於 \(x\in S\),找到 \(x\) 的點分樹子樹內和 \(x\) 在原樹距離不超過 \(f_u-dis(u,x)\) 的未刪除的點即可。

可以點分治的時候 BFS 求出按到 \(x\) 距離從小到大的序列,維護指標,每次詢問向後找即可。

指標總的偏移量不超過 \(O(n\log n)\),集合 \(S\) 的大小不超過 \(\log n\)

這樣對樹就有了 \(O(n\log n)\) 的做法了。

考慮加入一些非樹邊如何做。

如果非樹邊 \((x,y)\) 會對 \((u,v)\) 間的最短距離產生影響,那麼 \((u,v)\) 間的最短路徑一定經過 \(x\)\(y\)

所以對於每條非樹邊,取出 \(x\) ,總共 \(m-n+1\) 個這樣的點,以它為起點 BFS,類似地指標維護即可。

總的時間複雜度 \(O(n\log n+n(m-n))\)

參考程式碼

Submission #265206778 - Codeforces