题意:
1443 路径和树
1.5 秒 131,072.0 KB 80 分 5级题
给定一幅无向带权连通图G = (V, E) (这里V是点集,E是边集)。从点u开始的最短路径树是这样一幅图G1 = (V, E1),其中E1是E的子集,并且在G1中,u到所有其它点的最短路径与他在G中是一样的。
现在给定一幅无向带权连通图G和一个点u。你的任务是找出从u开始的最短路径树,并且这个树中所有边的权值之和要最小。
输入
单组测试数据。
第一行有两个整数n和m(1 ≤ n ≤ 310^5, 0 ≤ m ≤ 310^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
思路:
我的想法:
(1)这道题其实说的还是比较明白的,看到从一个点出发到所有点的最短路径,想到了什么,肯定就是dij算法了。。。。(堆优化的dij)
(2)看到树的所有边权值之和最小,想到了什么,最小生成树嘛。。
怎么把它们结合起来呢? 不知道了。。。。
正解:
(1)怎么结合起来呢?把最短路径的边找出来,在上面跑最小生成树啊!!!
(2)问题就是图的最短路径可能有很多条,那么哪一条才是最优的?
有向图的最小生成树算法---->最小树形图
啊啊啊啊!我没学过,要补一补。。。
(3)贪心也可以写
我们每次找除起点以外,进入这个点的边的最小值,那么合起来就是最小权值和
证明:
首先,最短路径树,肯定只有一个前驱,每个点都选边权最小的一条边一定是最优的。
其次,最短路径的图是一个有向无环图,我们选择n - 1条边一定是一棵树
代码实现:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<vector>
#include<stack>
#include<queue>
#include<cstring>
using namespace std;
typedef long long ll;
const int maxn = 3e5 + 5;
int n,m;
struct node{
int u;
int v;
int w;
int next;
}G[maxn*2];
int head[maxn];
int cnt = 0;
int be;
struct Node{
int id;
ll w;
int xid;
friend bool operator < (Node a,Node b){
return a.w > b.w;
}
};
priority_queue<Node> q;
ll dis[maxn];
ll pre[maxn];
vector<int> ans;
void add(int u,int v,int w){
G[cnt].u = u;
G[cnt].v = v;
G[cnt].w = w;
G[cnt].next = head[u];
head[u] = cnt++;
}
void dij(int x){
while(!q.empty()) q.pop();
for(int i = 1;i <= n;i++) dis[i] = 1e18,pre[i] = 1e18;
dis[x] = 0; pre[x] = 0;
q.push((Node){x,0,-1});
while(!q.empty()){
Node cur = q.top(); q.pop();
if(dis[cur.id] < cur.w) continue;
for(int i = head[cur.id];i != -1;i = G[i].next){
int v = G[i].v; int w = G[i].w;
if((dis[v] > dis[cur.id] + w)||((dis[v] == dis[cur.id] + w)&&pre[v] > w)){
dis[v] = dis[cur.id] + w;
pre[v] = w;
q.push((Node){v,dis[v],i});
}
}
}
}
int main(){
scanf("%d%d",&n,&m);
memset(head,-1,sizeof(head));
cnt = 0;
int u,v,w;
for(int i = 1;i <= m;i++){
scanf("%d%d%d",&u,&v,&w);
add(u,v,w);
add(v,u,w);
}
scanf("%d",&be);
dij(be);
ll ans = 0;
for(int i = 1;i <= n;i++){
ans = ans + pre[i];
}
printf("%lld\n",ans);
return 0;
}