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

題面在這裏就不放了。算法

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

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

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

當時思考了一下就快速寫完離線作法就去作其餘題了。code

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

事實上若是你瞭解這個東西,那你就能很快的給出解,那僅此以這道題做爲學習Kruskal重構樹的例子。排序

先給出一個經典的模型:get

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

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

固然也能夠用咱們要講的Kruskal重構樹來解決,算法雖不一樣,思想相似。string

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重構樹的構建,最小生成樹上最值問題的再探。
相關文章
相關標籤/搜索