【无码专区1】简单路径的第二大边权(启发式合并+最小生成树)
只有std,沒有自我實現,所以叫做無碼專區
description
給一張無向圖,多次詢問,每次詢問兩個點之間所有簡單路徑(不重復經過點)中邊權第二大(不是嚴格第二大)的權值的最小值。
數據范圍:10510^5105 級別
我的想法
前 50%50\%50% 的數據 q,n≤103,m≤2×103:q,n\le 10^3,m\le 2\times 10^3:q,n≤103,m≤2×103:
先做一次最小生成樹,求出任意兩點之間聯通的最小邊權(某條路徑的最大邊權值)。
每次詢問 (u,v)(u,v)(u,v) ,我直接枚舉中間轉折點 iii,強制這條路徑是 u→i→vu\rightarrow i\rightarrow vu→i→v。【→\rightarrow→ 代指一條路徑】
第二大邊權就是 (u,i)(u,i)(u,i) 聯通路徑的最大值和 (v,i)(v,i)(v,i) 聯通路徑的最大值,二者中的較小值。
旁邊的 Oxide\text{Oxide}Oxide 巨佬認為很有可能 u→iu\rightarrow iu→i 和 i→wi\rightarrow wi→w 之間經過了同樣的點。
i.e. u→x→i→x→vu\rightarrow x\rightarrow i \rightarrow x\rightarrow vu→x→i→x→v
但后面再仔細一想,就算這是的答案會被 iii 更新,但是后面一定會枚舉到 xxx,顯然u→x→vu\rightarrow x\rightarrow vu→x→v 會比以前的路徑少了 (x,i)(x,i)(x,i) 一段。
路徑上的邊權最大值一定是不減的
所以多的 (x,i)(x,i)(x,i) 一段只可能使最大邊權增大 / 不變。
那么 xxx 的決策一定是不劣于 iii 的決策的。
另有 20%20\%20% 是樹:兩個點之間只有一條簡單路徑,可以直接倍增求路徑的第二大邊權值。
綜上,本題自我實現分值應該在 70′70'70′。
solution
類似最小生成樹的方法,從小到大加邊。
如果加完一條邊后,u,vu,vu,v 之間只差一條邊聯通,那么顯然這條邊就是第二小,也就是最終的答案。
考慮怎么維護?
設 N(u):N(u):N(u): 與 uuu 有直接邊相連,但還沒有相連的點的集合【當前枚舉邊的權值暫時小于等于這些點與 uuu 的權值,最小生成樹的寫法就還沒有加入這些邊】
或者理解為:還差一條邊就能聯通的點的集合
考慮啟發式合并,每次合并 u,vu,vu,v 各自所在的連通塊。
此時可能出現:N(u)N(u)N(u) 中的點 xxx 與 vvv 相連【在 vvv 連通塊里面】 ,或,N(v)N(v)N(v) 中的點 yyy 與 uuu 相連【在 uuu 連通塊里面】
這個時候意味著,加上 u?vu-vu?v 這條邊后,還差 u?xu-xu?x 或 v?yv-yv?y 這一條邊就會使得 u,vu,vu,v 相連,所以 u?vu-vu?v 這條邊權就是最后的答案。
如果直接枚舉 N(u),N(v)N(u),N(v)N(u),N(v) 則不符合時間限制。
我們可以這么做:
-
遍歷 N(u)N(u)N(u) 的所有點,然后看是否在 vvv 的詢問中。
i.e. 假設 x∈N(u),x∈q(v)x\in N(u),x\in q(v)x∈N(u),x∈q(v) , x?ux-ux?u 之間的邊還沒有加入。
此時加入為 u?vu-vu?v 的邊。一旦加入完,x→u→vx\rightarrow u\rightarrow vx→u→v就只差 x?ux-ux?u 的一條邊。
所以答案就是現在操作的 u?vu-vu?v 邊的邊權。
這樣就處理了掛在 vvv 上面的某些 通過 uuu 連通塊中某些點和邊解決 的詢問。
-
遍歷 uuu 里面的所有詢問,判斷是否在 N(v)N(v)N(v) 中。
i.e. 假設 x∈q(u)x\in q(u)x∈q(u) , x?vx-vx?v 之間的邊還沒有加入。
此時加入為 u?vu-vu?v 的邊。一旦加入完,x→v→ux\rightarrow v\rightarrow ux→v→u 就只差 x?vx-vx?v 的一條邊。
所以答案是 u?vu-vu?v 現在這條邊的邊權。
這樣就處理了掛在 uuu 上面的某些 通過 vvv 連通塊中某些點和邊解決 的詢問。
這么做會發現,雖然是合并兩個聯通塊和處理兩個聯通塊各自掛著的詢問,但是枚舉的只有一個聯通塊的信息。
所以啟發式合并,就用 N(u)+q(u)N(u)+q(u)N(u)+q(u) 和 N(v)+q(v)N(v)+q(v)N(v)+q(v) 大小比較,選較小的進行枚舉。
時間復雜度 O(nlog?n)O(n\log n)O(nlogn)
合并具體而言:枚舉其中一個較小連通塊的信息,進行答案處理。所有掛在 u,vu,vu,v 點的詢問和邊都重新掛在合并后新連通塊的根 www 上。
i.e. 詢問 u,xu,xu,x 的答案,合并后相當于問 w,xw,xw,x 的答案,因為反正 u?wu-wu?w 的邊權不是第二大。原本 u?xu-xu?x 的一條邊,變成 w?xw-xw?x 的一條邊。
所以上面的形如 x?ux-ux?u :不一定表示原先加入的 mmm 條邊就是 x?ux-ux?u,而是可能通過 x?a?b?c?...?ux-a-b-c-...-ux?a?b?c?...?u ,不斷合并,可能代指的是一條簡單路徑。
參考code
#include <bits/stdc++.h> using namespace std;#define N 400005#define pb push_back int fa[N]; struct node {int x, y, z; } b[N]; int ans[N], n, m, Q; set<array<int, 2>> q[N]; set<int> e[N];int get(int x) {if (fa[x] == x)return x;return fa[x] = get(fa[x]); }inline bool cmp(node x, node y) {return x.z < y.z; }void combine(int x, int y, int val) {for (auto u : e[x]) {while (1) {auto it = q[y].lower_bound({u, -1});if (it == q[y].end() || (*it)[0] != u)break;int id = (*it)[1];ans[id] = val;assert(q[y].count({u, id}));assert(q[u].count({y, id}));q[y].erase(it);q[u].erase({y, id});}}vector<array<int, 2>> delq;for (auto u : q[x]) {if (e[y].count(u[0])) {ans[u[1]] = val;q[u[0]].erase({x, u[1]});delq.pb(u);}}for (auto u : delq)q[x].erase(u);fa[x] = y;for (auto v : e[x]) {assert(e[v].count(x));e[v].erase(x);if (v != y) {e[v].insert(y);e[y].insert(v);}}e[x].clear();for (auto v : q[x]) {assert(v[0] != y);assert(q[v[0]].count({x, v[1]}));q[v[0]].erase({x, v[1]});q[v[0]].insert({y, v[1]});q[y].insert({v[0], v[1]});}q[x].clear(); }int main() {freopen("path.in", "r", stdin);freopen("path.out", "w", stdout);scanf("%d%d%d", &n, &m, &Q);for (int i = 1; i <= n; i++)e[i].clear(), q[i].clear();for (int i = 1; i <= m; i++) {scanf("%d%d%d", &b[i].x, &b[i].y, &b[i].z);e[b[i].x].insert(b[i].y);e[b[i].y].insert(b[i].x);}for (int i = 1; i <= n; i++)fa[i] = i;sort(b + 1, b + 1 + m, cmp);for (int i = 1; i <= Q; i++) {ans[i] = 0;int x, y;scanf("%d%d", &x, &y);if (e[x].count(y)) {ans[i] = 1;continue;}q[x].insert({y, i});q[y].insert({x, i});}for (int i = 1; i <= m; i++) {b[i].x = get(b[i].x), b[i].y = get(b[i].y);if (b[i].x == b[i].y)continue;if (q[b[i].x].size() + e[b[i].x].size() > q[b[i].y].size() + e[b[i].y].size())swap(b[i].x, b[i].y);combine(b[i].x, b[i].y, b[i].z + 1);}for (int i = 1; i <= Q; i++)printf("%d\n", ans[i] - 1); }總結
以上是生活随笔為你收集整理的【无码专区1】简单路径的第二大边权(启发式合并+最小生成树)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 把路由器IP改成和电脑IP同一网段的方法
- 下一篇: 【无码专区2】序列划分(数学)