最短路径树定义、性质、模板及例题
最短路径树的定义
给定一个无向连通带权图\(G = (V, E)\),节点\(u\)的最短路径树可以定义为:
一个图\(G\)的生成树\(G_1 = (V, E_1)\),其中\(E_1\)为\(E\)的子集。在\(G_1\)中从点\(u\)到其他任何点的最短距离与在\(G\)中相同。
跑一遍Dijkstra算法,使用数组\(pre\)记录每个点是有哪条边更新的。
性质
-
根节点到其他所有点的最短距离与原图中相同
证明:定义 -
在所有生成树中,最短路径树满足根节点到其他所有点的距离之和最短(Atcoder ABC252 E)
证明:由于根节点到其他任何点的距离都是原图的最短距离,因此距离之和也一定是最短的。
模板
void dijkstra()
{
priority_queue<pair<long long, int>, vector<pair<long long, int>>, greater<pair<long long, int>> > heap;
for(int i = 1; i <= n; i ++) d[i] = 1e18;
heap.push({0, 1});
d[1] = 0;
while(heap.size()) {
auto t = heap.top();
heap.pop();
int ver = t.second;
ll dist = t.first;
if(st[ver]) continue;
st[ver] = true;
for(int i = h[ver]; ~i; i = ne[i]) {
int j = e[i];
if(d[j] > dist + w[i]) {
d[j] = dist + w[i];
heap.push({d[j], j});
pre[j] = i;
}
}
}
}
例题
- CF545E Paths and Trees(权值和最小的最短路径树)
思路:在更新的时候,如果d[j] = dist + w[i],则用权值最小的边来更新
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
typedef long long ll;
typedef pair<ll, int> pii;
const int N = 300010, M = 2 * N;
int n, m, u;
int h[N], e[M], ne[M], idx;
ll w[M], d[N];
int pre[N];
bool st[N], isin[M];
void add(int a, int b, ll c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}
void dijkstra(int u)
{
priority_queue<pii, vector<pii>, greater<pii> > heap;
heap.push({0, u});
for(int i = 1; i <= n; i ++) d[i] = 1e18;
d[u] = 0;
while(heap.size()) {
auto t = heap.top();
heap.pop();
int ver = t.second;
ll dist = t.first;
if(st[ver]) continue;
st[ver] = true;
for(int i = h[ver]; ~i; i = ne[i]) {
int j = e[i];
if(d[j] > dist + w[i]) {
d[j] = dist + w[i];
heap.push({d[j], j});
pre[j] = i;
}
else if(d[j] == dist + w[i]) {
if(w[i] < w[pre[j]]) {
pre[j] = i;
}
}
}
}
}
int main()
{
scanf("%d%d", &n, &m);
memset(h, -1, sizeof h);
for(int i = 0; i < m; i ++) {
int a, b;
ll c;
scanf("%d%d%lld", &a, &b, &c);
add(a, b, c), add(b, a, c);
}
scanf("%d", &u);
dijkstra(u);
ll ans = 0;
for(int i = 1; i <= n; i ++) {
if(i != u) {
ans += w[pre[i]];
}
}
printf("%lld\n", ans);
for(int i = 1; i <= n; i ++) {
if(i != u) {
isin[pre[i] / 2] = true;
}
}
for(int i = 0; i < m; i ++) {
if(isin[i]) {
printf("%d ", i + 1);
}
}
printf("\n");
return 0;
}
- Acwing349 黑暗城堡(最短路径树计数)
思路:对每个点可选边的数量计数。在跑Dijkstra算法的时候,如果d[j] = dist + w[i],那么当前点可选边数量+1;如果d[j] > dist + w[i],说明边权需要更新,则当前点可选边数量归1。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
typedef long long ll;
typedef pair<ll, int> pii;
const int N = 1010, M = N * N;
const ll mod = 2147483647;
int n, m;
int h[N], e[M], ne[M], idx;
ll w[M], d[N], cnt[N];
bool st[N];
void add(int a, int b, ll c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}
void dijkstra()
{
priority_queue<pii, vector<pii>, greater<pii> > heap;
heap.push({0, 1});
for(int i = 1; i <= n; i ++) d[i] = 1e18;
d[1] = 0;
while(heap.size()) {
auto t = heap.top();
heap.pop();
int ver = t.second;
ll dist = t.first;
if(st[ver]) continue;
st[ver] = true;
for(int i = h[ver]; ~i; i = ne[i]) {
int j = e[i];
if(d[j] > dist + w[i]) {
d[j] = dist + w[i];
heap.push({d[j], j});
cnt[j] = 1;
}
else if(d[j] == dist + w[i]) {
cnt[j] ++;
}
}
}
}
int main()
{
scanf("%d%d", &n, &m);
memset(h, -1, sizeof h);
for(int i = 0; i < m; i ++) {
int a, b;
ll c;
scanf("%d%d%lld", &a, &b, &c);
add(a, b, c), add(b, a, c);
}
dijkstra();
ll ans = 1;
for(int i = 2; i <= n; i ++) {
ans = ans * cnt[i] % mod;
}
printf("%lld\n", ans);
return 0;
}
- CF1005F Berland and the Shortest Paths(输出k条最短路径树的方案)
思路:记录每个点的可选边。Dijkstra之后,通过dfs枚举方案。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <vector>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int N = 200010, M = 2 * N;
int n, m, k;
int h[N], e[M], ne[M], idx;
bool st[N];
int d[N];
vector<int> pre[N];
int ans[M];
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}
void dijkstra()
{
priority_queue<pii, vector<pii>, greater<pii> > heap;
heap.push({0, 1});
for(int i = 1; i <= n; i ++) d[i] = 1e9;
d[1] = 0;
while(heap.size()) {
auto t = heap.top();
heap.pop();
int ver = t.second, dist = t.first;
if(st[ver]) continue;
st[ver] = true;
for(int i = h[ver]; ~i; i = ne[i]) {
int j = e[i];
if(d[j] > dist + 1) {
d[j] = dist + 1;
pre[j].clear();
pre[j].push_back(i);
heap.push({d[j], j});
}
else if(d[j] == dist + 1) {
pre[j].push_back(i);
}
}
}
}
void dfs(int u)
{
if(u > n) {
if(!k) exit(0);
for(int i = 0; i < m; i ++) {
printf("%d", ans[i]);
}
printf("\n");
k --;
return;
}
for(auto t : pre[u]) {
ans[t / 2] = 1;
dfs(u + 1);
ans[t / 2] = 0;
}
}
int main()
{
scanf("%d%d%d", &n, &m, &k);
memset(h, -1, sizeof h);
for(int i = 0; i < m; i ++) {
int a, b;
scanf("%d%d", &a, &b);
add(a, b), add(b, a);
}
dijkstra();
ll num = 1;
for(int i = 2; i <= n; i ++) {
num = num * (ll)pre[i].size();
if(num > k) {
num = k;
break;
}
}
k = num;
printf("%d\n", k);
dfs(2);
return 0;
}
- CF1076D Edge Deletion
题意:给定一个\(n\)个点\(m\)条边的无向连通带权图, 要求删边至最多剩余\(k\)条边。定义“好点”是指删边后,\(1\)号节点到它的最短路长度仍然等于原图最短路长度的节点。要求满足给定条件下,删边后“好点”个数最多,求一个满足要求的图。
思路:先求出最短路径树。然后从根开始做DFS,直到剩余k条边为止。DFS到的边即为需要剩余的边。