1. 程式人生 > 其它 >最短路徑樹定義、性質、模板及例題

最短路徑樹定義、性質、模板及例題

最短路徑樹的定義

給定一個無向連通帶權圖\(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;
}