最短路

经典图论

例题

P6833 [Cnoi2020] 雷雨

枚举三条路径交,求最小值即可。

最短路树

按松弛操作记录一个点的前驱,将前驱作为父亲建树。

性质

  • 形态不唯一
  • 根节点到其他所有点的最短距离与原图中相同
  • 在所有生成树中,最短路径树满足根节点到其他所有点的距离之和最短

例题

AT_abc252_e [ABC252E] Road Reduction

构造最短路树即可。

#include <bits/stdc++.h>
#define int long long
#define pb push_back
using namespace std;

const int N = 2e5 + 5;
int n, m, t, u, v, w, a[N], sz[N], fa[N], dis[N];
bool vis[N];
struct Node {
    int v, w;
} ;
vector<Node> g[N];
vector<int> ans, G[N];
map<pair<int, int>, int> mp;
priority_queue<pair<int, int>, vector<pair<int, int> >, greater<pair<int, int> > > q;

inline void dijkstra(int s) {
    fill(dis + 1, dis + 1 + n, 1e18);
    dis[s] = 0;
    q.push({0ll, s});

    while(! q.empty()) {
        auto [_, u] = q.top();
        q.pop();

        if(vis[u]) continue ;

        vis[u] = true;

        for(auto [x, w] : g[u]) {
            if(dis[u] + w < dis[x]) {
                fa[x] = u;
                dis[x] = dis[u] + w;
                q.push({dis[x], x});
            }
            else if(dis[u] + w == dis[x]) fa[x] = min(fa[x], u);
        }
    }

    return ;
}

signed main() {
    ios_base :: sync_with_stdio(NULL);
    cin.tie(nullptr);
    cout.tie(nullptr);

    cin >> n >> m;
    for(int i = 1 ; i <= m ; ++ i) {
        cin >> u >> v >> w;

        g[u].pb({v, w}), g[v].pb({u, w});

        mp[{u, v}] = mp[{v, u}] = i;
    }
    
    dijkstra(1);

    for(int i = 2 ; i <= n ; ++ i)
        ans.pb(mp[{i, fa[i]}]);

    for(auto i : ans) cout << i << ' ';

    return 0;
}

P5201 [USACO19JAN] Shortcut G

在最短路树上考虑问题,那么问题转化为求 \(\max \{ (dis_i - T) \times sz_i \}\)

题目要求最短路的字典序最小,在记录最短路前驱的时候顺便维护即可。

代码:

#include <bits/stdc++.h>
#define int long long
#define pb push_back
using namespace std;

const int N = 1e4 + 5;
int n, m, t, u, v, w, ans, a[N], sz[N], fa[N], dis[N];
bool vis[N];
struct Node {
    int v, w;
} ;
vector<int> G[N];
vector<Node> g[N];
priority_queue<pair<int, int>, vector<pair<int, int> >, greater<pair<int, int> > > q;

inline void dijkstra(int s) {
    fill(dis + 1, dis + 1 + n, 1e18);
    dis[s] = 0;
    q.push({0ll, s});

    while(! q.empty()) {
        auto [_, u] = q.top();
        q.pop();

        if(vis[u]) continue ;

        vis[u] = true;

        for(auto [x, w] : g[u]) {
            if(dis[u] + w < dis[x]) {
                fa[x] = u;
                dis[x] = dis[u] + w;
                q.push({dis[x], x});
            }
            else if(dis[u] + w == dis[x]) fa[x] = min(fa[x], u);
        }
    }

    return ;
}

inline void dfs(int x, int last) {
    sz[x] = a[x];

    for(auto u : G[x])
        if(u != last) {
            dfs(u, x);

            sz[x] += sz[u];
        }

    ans = max(ans, (dis[x] - t) * sz[x]);

    return ;
}

signed main() {
    ios_base :: sync_with_stdio(NULL);
    cin.tie(nullptr);
    cout.tie(nullptr);

    cin >> n >> m >> t;
    for(int i = 1 ; i <= n ; ++ i)
        cin >> a[i];    
    for(int i = 1 ; i <= m ; ++ i) {
        cin >> u >> v >> w;

        g[u].pb({v, w}), g[v].pb({u, w});
    }
    
    dijkstra(1);
    
    for(int i = 1 ; i <= n ; ++ i)
        G[i].pb(fa[i]), G[fa[i]].pb(i);

    dfs(1, -1);

    cout << ans;

    return 0;
}

CF545E Paths and Trees

仍然是固定树的形态,一个点有多个前驱时优先考虑边权小的即可。

代码:

#include <bits/stdc++.h>
#define int long long
#define pb push_back
using namespace std;

const int N = 3e5 + 5;
int n, m, t, u, v, w, S, a[N], sz[N], fa[N], dis[N], val[N];
bool vis[N];
struct Node {
    int v, w;
} ;
vector<Node> g[N];
vector<int> ans, G[N];
map<pair<int, int>, pair<int, int> > mp;
priority_queue<pair<int, int>, vector<pair<int, int> >, greater<pair<int, int> > > q;

inline void dijkstra(int s) {
    fill(dis + 1, dis + 1 + n, 1e18);
    dis[s] = 0;
    q.push({0ll, s});

    while(! q.empty()) {
        auto [_, u] = q.top();
        q.pop();

        if(vis[u]) continue ;

        vis[u] = true;

        for(auto [x, w] : g[u]) {
            if(dis[u] + w < dis[x]) {
                fa[x] = u;
                dis[x] = dis[u] + w;
                q.push({dis[x], x});
                val[x] = mp[{u, x}].second;
            }
            else if(dis[u] + w == dis[x]) {
                int k = mp[{u, x}].second;

                if(val[x] > k) {
                    fa[x] = u;
                    val[x] = k;
                }
            }
        }
    }

    return ;
}

signed main() {
    ios_base :: sync_with_stdio(NULL);
    cin.tie(nullptr);
    cout.tie(nullptr);

    cin >> n >> m;
    for(int i = 1 ; i <= m ; ++ i) {
        cin >> u >> v >> w;

        g[u].pb({v, w}), g[v].pb({u, w});

        mp[{u, v}] = mp[{v, u}] = {i, w};
    }
    cin >> S;
    
    dijkstra(S);

    int GeorgeAAAADHD = 0;

    for(int i =1 ; i <= n ; ++ i)
        if(i != S) ans.pb(mp[{i, fa[i]}].first), GeorgeAAAADHD += mp[{i, fa[i]}].second;

    cout << GeorgeAAAADHD << '\n';
    for(auto i : ans) cout << i << ' ';

    return 0;
}

CF1076D Edge Deletion

在最短路树上保留 \(k\) 条边即可。

代码:

#include <bits/stdc++.h>
#define int long long
#define pb push_back
using namespace std;

const int N = 3e5 + 5;
int n, m, k, u, v, w, cnt, a[N], sz[N], fa[N], dis[N];
bool vis[N];
struct Node {
    int v, w;
} ;
vector<Node> g[N];
vector<int> ans, G[N];
map<pair<int, int>, int> mp; 
priority_queue<pair<int, int>, vector<pair<int, int> >, greater<pair<int, int> > > q;

inline void dijkstra(int s) {
    fill(dis + 1, dis + 1 + n, 1e18);
    dis[s] = 0;
    q.push({0ll, s});

    while(! q.empty()) {
        auto [_, u] = q.top();
        q.pop();

        if(vis[u]) continue ;

        vis[u] = true;

        for(auto [x, w] : g[u]) {
            if(dis[u] + w < dis[x]) {
                fa[x] = u;
                dis[x] = dis[u] + w;
                q.push({dis[x], x});
            }
        }
    }

    return ;
}

inline void dfs(int x, int last) {
    cnt += (vis[x] == 0);
    if(last > 0 && (int)ans.size() < k) ans.pb(mp[{x, last}]);
    if(cnt > k) return ;
    
    vis[x] = true;

    for(auto u : G[x])
        if(u != last) dfs(u, x);

    return ;
}

signed main() {
    ios_base :: sync_with_stdio(NULL);
    cin.tie(nullptr);
    cout.tie(nullptr);

    cin >> n >> m >> k;
    for(int i = 1 ; i <= m ; ++ i) {
        cin >> u >> v >> w;

        g[u].pb({v, w}), g[v].pb({u, w});

        mp[{u, v}] = mp[{v, u}] = i;
    }
    
    dijkstra(1);

    for(int i = 2 ; i <= n ; ++ i)
        G[i].pb(fa[i]), G[fa[i]].pb(i);

    fill(vis + 1, vis + 1 + n, false);

    dfs(1, -1);

    cout << ans.size() << '\n';

    for(auto i : ans) cout << i << ' ';

    return 0;
}

P6880 [JOI 2020 Final] 奥运公交 / Olympic Bus

建出 \(1 \rightsquigarrow n\)\(n \rightsquigarrow 1\) 的最短路树,可以将图上的边划分为最短路树边和非最短路树边。

考虑枚举翻转哪一条边:

  • 非树边:

不会对答案产生贡献。

  • 树边:

重新跑最短路即可,最多有 \(2 \times n - 2\) 条边。

时间复杂度 \(O(m + n ^ 3)\)

CF1005F Berland and the Shortest Paths

类 dijkstra 算法

例题

P4042 [AHOI2014/JSOI2014] 骑士游戏

P2446 [SDOI2010] 大陆争霸

CF1693C Keshi in Search of AmShZ

P8860 动态图连通性

posted @ 2025-10-18 11:33  endswitch  阅读(2)  评论(0)    收藏  举报