最短路径树定义、性质、模板及例题

最短路径树的定义

给定一个无向连通带权图\(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;
            }
        }
    }
}

例题

  1. 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;
}
  1. 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;
}
  1. 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;
}
  1. CF1076D Edge Deletion
    题意:给定一个\(n\)个点\(m\)条边的无向连通带权图, 要求删边至最多剩余\(k\)条边。定义“好点”是指删边后,\(1\)号节点到它的最短路长度仍然等于原图最短路长度的节点。要求满足给定条件下,删边后“好点”个数最多,求一个满足要求的图。

思路:先求出最短路径树。然后从根开始做DFS,直到剩余k条边为止。DFS到的边即为需要剩余的边。

posted @ 2022-05-28 20:40  pbc的成长之路  阅读(594)  评论(0编辑  收藏  举报