【NOI 2018】歸程(Kruskal重構樹)


題面在這里就不放了。

同步賽在做這個題的時候,心里有點糾結,很容易想到離線的做法,將邊和詢問一起按水位線排序,模擬水位下降,維護當前的各個聯通塊中距離$1$最近的距離,每次遇到詢問時輸出所在聯通塊的信息。

離線的思路對滿分做法有一定的啟發性,很容易想到將並查集持久化一下就能支持在線了。

但是這個是兩個$log$的,有卡常的風險也不是很方便寫。

當時思考了一下就快速寫完離線做法就去做其他題了。

對於這道題,有一個更好的做法:Kruskal重構樹。

事實上如果你了解這個東西,那你就能很快的給出解,那僅此以這道題作為學習Kruskal重構樹的例子。

先給出一個經典的模型:

  • 給定一張無向圖,每次詢問兩點之間所有簡單路徑中最大邊權的最小值。

一個常規的做法就是建出最小生成樹,答案就是樹上路徑的最大邊權,正確性顯然。

當然也可以用我們要講的Kruskal重構樹來解決,算法雖不同,思想類似。

Kruskal中我們連接兩個聯通塊(子樹)時直接用一條邊將對應的兩個點相連,但在Kruskal重構樹中,我們先建一個虛點作為兩個子樹的樹上父親,讓兩個聯通塊分別與該點相連,注意的是要維護並查集合並時的有序性。

我們稱新建的虛點為方點,代表了原圖中的一條邊,原圖中的點為圓點,則該樹有一些優雅的性質:

  1. 這是一顆二叉樹,並且相當於一個堆,因為邊是有順序合並的。
  2. 最小生成樹上路徑的邊權信息轉化成了點權信息。

那么回顧剛剛的那個模型,每個詢問就相當於回答Kruskal重構樹上兩點$lca$的權值。

 

最后在回來看這道題,就顯得十分輕松了。

我們把每條邊按照水位線從高到低依次插入,可以發現每次規定一個水位下限,車子能走的范圍對應了Kruskal重構樹上的一棵子樹,那么每次只要倍增找到最緊的那個限制的點就可以了。

  1 #include <cstdio>
  2 #include <queue>
  3 #include <cstring>
  4 #include <algorithm>
  5 
  6 typedef long long LL;
  7 const int N = 600005, INF = 2e9 + 7, LOG = 21;
  8 
  9 int tc, n, m, Qi, k, s;
 10 int dis[N], flg[N], val[N], mdi[N], gr[LOG][N];
 11 std::priority_queue<std::pair<int, int> > Q;
 12 
 13 inline void Read(int &x) {
 14     x = 0; static char c;
 15     for (c = getchar(); c < '0' || c > '9'; c = getchar());
 16     for (; c >= '0' && c <= '9'; x = (x << 3) + (x << 1) + c - '0', c = getchar());
 17 }
 18 
 19 struct Edge {
 20     int u, v, a;
 21     inline friend bool operator < (Edge a, Edge b) {
 22         return a.a > b.a;
 23     }
 24 } e[N];
 25 
 26 int yun, las[N], to[N << 1], pre[N << 1], wi[N << 1];
 27 inline void Add(int a, int b, int c = 0) {
 28     to[++yun] = b; wi[yun] = c; pre[yun] = las[a]; las[a] = yun;
 29 }
 30 void Gragh_clear() {
 31     memset(gr, 0, sizeof gr);
 32     memset(las, 0, sizeof las);
 33     yun = 0;
 34 }
 35 
 36 namespace DSU {
 37     int fa[N];
 38     void Init() {
 39         for (int i = 1; i <= n + m; ++i) {
 40             fa[i] = i;
 41             if (i <= n) mdi[i] = dis[i], val[i] = -1;
 42         }
 43     }
 44     int Seek(int x) {
 45         return (x == fa[x])? (x) : (fa[x] = Seek(fa[x]));
 46     }
 47     void Merge(int x, int y) {
 48         fa[Seek(y)] = x;
 49     }
 50 }
 51 
 52 void Dij() {
 53     for (int i = 1; i <= n; ++i) {
 54         dis[i] = INF; flg[i] = 0;
 55     }
 56     dis[1] = 0;
 57     Q.push(std::make_pair(0, 1));
 58     for (; !Q.empty(); ) {
 59         int x = Q.top().second; Q.pop();
 60         if (flg[x]) continue;
 61         flg[x] = 1;
 62         for (int i = las[x]; i; i = pre[i]) {
 63             if (dis[to[i]] > dis[x] + wi[i]) {
 64                 dis[to[i]] = dis[x] + wi[i];
 65                 Q.push(std::make_pair(-dis[to[i]], to[i]));
 66             }
 67         }
 68     }
 69 }
 70 
 71 int main() {
 72     freopen("return.in", "r", stdin);
 73     freopen("return.out", "w", stdout);
 74     
 75     scanf("%d", &tc);
 76     for (; tc; --tc) {
 77         scanf("%d%d", &n, &m);
 78         Gragh_clear();
 79         for (int i = 1, x, y, a, l; i <= m; ++i) {
 80             //scanf("%d%d%d%d", &x, &y, &l, &a);
 81             Read(x); Read(y); Read(l); Read(a);
 82             Add(x, y, l); Add(y, x, l);
 83             e[i] = (Edge) { x, y, a };
 84         }
 85         Dij(); DSU::Init();
 86         
 87         std::sort(e + 1, e + 1 + m);
 88         for (int i = 1; i <= m; ++i) {
 89             int x = DSU::Seek(e[i].u), y = DSU::Seek(e[i].v);
 90             if (x == y) continue;
 91             val[i + n] = e[i].a;
 92             mdi[i + n] = std::min(mdi[x], mdi[y]);
 93             DSU::Merge(i + n, x); DSU::Merge(i + n, y);
 94             gr[0][x] = gr[0][y] = i + n;
 95         }
 96         
 97         for (int i = 1; i < LOG; ++i) {
 98             for (int j = 1; j <= n + m; ++j) {
 99                 if (gr[i - 1][j]) gr[i][j] = gr[i - 1][gr[i - 1][j]];
100             }
101         }
102         
103         scanf("%d%d%d", &Qi, &k, &s);
104         for (int x, a, lans = 0; Qi; --Qi) {
105             //scanf("%d%d", &x, &a);
106             Read(x); Read(a);
107             x = (x + (LL) k * lans - 1) % n + 1;
108             a = (a + (LL) k * lans) % (s + 1);
109             for (int i = LOG - 1; ~i; --i) {
110                 if (gr[i][x] && val[gr[i][x]] > a) x = gr[i][x];
111             }
112             printf("%d\n", mdi[x]);
113             lans = mdi[x];
114         }
115     }
116     
117     return 0;
118 }
View Code

 

$\bigodot$技巧&套路:

  • kruskal重構樹的構建,最小生成樹上最值問題的再探。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM