最短路
经典图论
例题
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)\)。