P1967 [NOIP2013 提高組] 貨車運輸
原題地址
思路:
由於題目要求的是使兩點之間的最小邊權最大,所以可以構造最大生成樹(最大生成樹一定是最大瓶頸生成樹,而瓶頸生成樹上兩點之間的路徑,在原圖中的所有路徑中,最小邊權仍然最大,即滿足題目要求,詳見 https://oi-wiki.org/graph/mst/#瓶頸生成樹 ),答案為最大生成樹上的路徑的最小邊權。
求樹上路徑上的邊權最值可以使用 \(LCA\),因為它可以透過合併兩個點的 \(LCA\) 和這兩個點到 \(LCA\) 的路徑的邊權最值得來。
做法:
先用 \(Kruskal\) 算出最大生成樹,再用倍增的方式計算 \(LCA\),並維護每條路徑上的最小邊權。
\(e\) 表示初始圖,\(G\) 表示最大生成樹,\(deep\) 表示點的深度,\(fa_{i,j}\) 表示 \(i\) 的第 \(2^j\) 級祖先,\(w_{i,j}\) 表示 \(i\) 到 \(i\) 的第 \(2^j\) 級祖先的路徑上的最小邊權,在求 \(LCA\) 的過程中輸出經過的點上的最小邊權即可。
程式碼:
時間複雜度:\(\Theta(m\log m + n\log n)\)
#include<iostream>
#include<vector>
#include<algorithm>
#define int long long
using namespace std;
const int N = 10010;
const int E = 50010;
const int M = 25;
const int INF = 1e18;
struct Node { int v, w; } ;
struct Edge { int u, v, w; } ;
int n, m, q;
int f[N], fa[N][M], deep[N], w[N][M];
bool vis[N];
Edge e[E];
vector<Node> G[N];
// 排序初始邊,用於求最大生成樹
bool cmpk(Edge x, Edge y) { return x.w > y.w; }
int find(int x) // 並查集 - 查詢祖先
{
if(f[x] == x)
return f[x];
return f[x] = find(f[x]);
}
void kruskal() // 求最大生成樹
{
sort(e+1, e+m+1, cmpk);
for(int i=1; i<=n; i++)
f[i] = i;
for(int i=1; i<=m; i++)
{
int u = e[i].u, v = e[i].v, w = e[i].w;
if(find(u) == find(v))
continue;
f[find(u)] = find(v);
G[u].push_back({v, w});
G[v].push_back({u, w});
}
}
void predfs(int u) // LCA初始化1 - 求出深度、父親
{
vis[u] = true;
for(int i=0; i<G[u].size(); i++)
{
int v = G[u][i].v;
if(vis[v])
continue;
deep[v] = deep[u] + 1;
fa[v][0] = u;
w[v][0] = G[u][i].w;
predfs(v);
}
}
int LCA(int x, int y) // 求LCA和答案
{
if(find(x) != find(y))
return -1;
int ans = INF;
if(deep[x] > deep[y])
swap(x, y);
for(int i=20; i>=0; i--)
{
if(deep[fa[y][i]] < deep[x])
continue;
ans = min(ans, w[y][i]);
y = fa[y][i];
}
if(x == y)
return ans;
for(int i=20; i>=0; i--)
{
if(fa[x][i] == fa[y][i])
continue;
ans = min(ans, min(w[x][i], w[y][i]));
x = fa[x][i];
y = fa[y][i];
}
ans = min(ans, min(w[x][0], w[y][0]));
return ans;
}
void prelca() // LCA初始化2 - 求出祖先
{
for(int i=1; i<=20; i++)
{
for(int j=1; j<=n; j++)
{
fa[j][i] = fa[fa[j][i-1]][i-1];
w[j][i] = min(w[j][i-1], w[fa[j][i-1]][i-1]);
}
}
}
signed main()
{
cin >> n >> m;
for(int i=1; i<=m; i++)
cin >> e[i].u >> e[i].v >> e[i].w;
kruskal();
for(int i=1; i<=n; i++)
{
if(vis[i])
continue;
deep[i] = 1;
predfs(i);
fa[i][0] = i;
w[i][0] = INF;
}
prelca();
cin >> q;
while(q --)
{
int x, y; cin >> x >> y;
cout << LCA(x, y) << '\n';
}
return 0;
}