题目链接:https://www.51nod.com/Challenge/Problem.html#!#problemId=1443

  • 时间限制 2 秒 空间限制 131,072 KB

题目描述

给定一幅无向带权连通图G = (V, E) (这里V是点集,E是边集)。从点u开始的最短路径树是这样一幅图G1 = (V, E1),其中E1是E的子集,并且在G1中,u到所有其它点的最短路径与他在G中是一样的。
现在给定一幅无向带权连通图G和一个点u。你的任务是找出从u开始的最短路径树,并且这个树中所有边的权值之和要最小。

输入

单组测试数据。
第一行有两个整数n和m(1 ≤ n ≤ 3*10^5, 0 ≤ m ≤ 3*10^5),表示点和边的数目。
接下来m行,每行包含3个整数 ui, vi, wi ,表示ui和vi之间有一条权值为wi的无向边(1 ≤ ui,vi ≤ n, 1 ≤ wi ≤ 10^9)。
输入保证图是连通的。
最后一行给出一个整数u (1 ≤ u ≤ n),表示起点。

输出

输出这棵树的最小的权值之和。

输入样例

3 3
1 2 1
2 3 1
1 3 2
3

输出样例

2

解题思路

首先先跑一遍最短路,最短路上的边构成最短路径树,然后在最短路径树里跑一遍最小生成树就行了。实际上只要求出除起点外每个点选出最短的边加起来就行了。

#include <queue>
#include <cstring>
#include <iostream>
#define N 300005
using namespace std;
struct edge {
    int u, v, w;
}e[2 * N];
long long dis[N];
int cnt = 0, pre[N], vis[N], f[N];
void Add(int u, int v, int w)
{
    e[++cnt] = (edge){f[u], v, w};
    f[u] = cnt;
}
void Spfa(int s, int n)
{
    int t, v;
    queue <int> Q;
    Q.push(s);
    vis[s] = 1;
    dis[s] = 0;
    while (!Q.empty())
    {
        t = Q.front();
        Q.pop();
        vis[t] = 0;
        for (int i = f[t]; i; i = e[i].u)
        {
            v = e[i].v;
            if (dis[v] > dis[t] + e[i].w)
            {
                dis[v] = dis[t] + e[i].w;
                pre[v] = e[i].w;
                if (!vis[v])
                {
                    vis[v] = 1;
                    Q.push(v);
                }
            }
            else if (dis[v] == dis[t] + e[i].w)
                pre[v] = min(pre[v], e[i].w);
        }
    }
}
int main()
{
    long long ans = 0;
    int n, m, s, u, v, w;
    scanf("%d%d", &n, &m);
    memset(dis, 15, sizeof(dis));
    for (int i = 0; i < m; i++)
    {
        scanf("%d%d%d", &u, &v, &w);
        Add(u, v, w);
        Add(v, u, w);
    }
    scanf("%d", &s);
    Spfa(s, n);
    for (int i = 1; i <= n; i++)
        if (i != s)
            ans += (long long)pre[i];
    printf("%lld\n", ans);
    return 0;
}