51nod 路径和树1443

题意:

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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值