只有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(nlogn)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);
}