P1967 [NOIP2013 提高組] 貨車運輸 題解

gctiruct發表於2024-04-02

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;
}

相關文章