日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

【无码专区1】简单路径的第二大边权(启发式合并+最小生成树)

發布時間:2023/12/3 编程问答 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【无码专区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,n103,m2×103:

先做一次最小生成樹,求出任意兩點之間聯通的最小邊權(某條路徑的最大邊權值)。

每次詢問 (u,v)(u,v)(u,v) ,我直接枚舉中間轉折點 iii,強制這條路徑是 u→i→vu\rightarrow i\rightarrow vuiv。【→\rightarrow 代指一條路徑】

第二大邊權就是 (u,i)(u,i)(u,i) 聯通路徑的最大值和 (v,i)(v,i)(v,i) 聯通路徑的最大值,二者中的較小值。

旁邊的 Oxide\text{Oxide}Oxide 巨佬認為很有可能 u→iu\rightarrow iuii→wi\rightarrow wiw 之間經過了同樣的點。

i.e. u→x→i→x→vu\rightarrow x\rightarrow i \rightarrow x\rightarrow vuxixv

但后面再仔細一想,就算這是的答案會被 iii 更新,但是后面一定會枚舉到 xxx,顯然u→x→vu\rightarrow x\rightarrow vuxv 會比以前的路徑少了 (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) 中的點 xxxvvv 相連【在 vvv 連通塊里面】 ,或,N(v)N(v)N(v) 中的點 yyyuuu 相連【在 uuu 連通塊里面】

這個時候意味著,加上 u?vu-vu?v 這條邊后,還差 u?xu-xu?xv?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)xN(u),xq(v)x?ux-ux?u 之間的邊還沒有加入。

    此時加入為 u?vu-vu?v 的邊。一旦加入完,x→u→vx\rightarrow u\rightarrow vxuv就只差 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)xq(u)x?vx-vx?v 之間的邊還沒有加入。

    此時加入為 u?vu-vu?v 的邊。一旦加入完,x→v→ux\rightarrow v\rightarrow uxvu 就只差 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】简单路径的第二大边权(启发式合并+最小生成树)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。