最短路树+并查集 - 安全出行Safe Travel(usaco 2009)

描述
在这里插入图片描述
输入
第一行: 两个空格分开的数, N和M

第2…M+1行: 三个空格分开的数a_i, b_i,和t_i

输出
第1…N-1行: 第i行包含一个数:从牛棚_1到牛棚_i+1并且避免从牛棚1到牛棚i+1最短路经上最后一条牛路的最少的时间.如果这样的路经不存在,输出-1.
样例输入
4 5
1 2 2
1 3 2
3 4 4
3 2 1
2 4 3
样例输出
3
3
6


Analysis

只要题目中说了源点到每个点最短路径是唯一的 那往往都和最短路树有关系
我们就先跑一遍Dijkstra建出最短路树
(事实上也并不是真正意义的建树,其实就是明确每个点的父亲)
然后由于这道题我们不可以走原最短路的最后一条
我们只能通过非树边进行更新,得到“次短路”
在这里插入图片描述
这样,我们要找的节点i的去掉最短路最后一条边后的最短路,就是 d i s [ u ] + w [ u , v ] + d i s [ v ] − d i s [ i ] . dis[u]+w[u,v]+dis[v]−dis[i] . dis[u]+w[u,v]+dis[v]dis[i].其中,dis[] 代表节点到根节点的最短路径,w[u,v] 代表u 到v 的路径长度;
接着我们发现对于每一条非树边其 d i s [ u ] + w [ u , v ] + d i s [ v ] dis[u]+w[u,v]+dis[v] dis[u]+w[u,v]+dis[v]是一个定值,因而我们可以预先算出来,然后从小到大排序,枚举加入更新
一个很显然的事实,如果一个点在之前被更新过了,那么下一次枚举的非树边就不可以再更新它(因为我们是按照从小到大排的序)
我们用并查集加以维护即可


Code

/*created by xly*/

#include<bits/stdc++.h>
#define in read()
#define re register
#define ll long long 
using namespace std;
inline int read(){
	char ch;int f=1,res=0;
	while((ch=getchar())<'0'||ch>'9') if(ch=='-') f=-1;
	while(ch>='0'&&ch<='9'){
		res=(res<<1)+(res<<3)+(ch^48);
		ch=getchar();
	}
	return f==1?res:-res;
}
const int N=1e5+10,M=4e5+10;
bool vis[N];
int n,m,tfa[N],d[N],fa[N],ans[N];
int nxt[M],head[N],ecnt=0,cnt=0;
struct node{int u,v,w;}e[M],tree[M];
inline bool cmp(const node &a,const node &b){return a.w<b.w;}
inline void add(int x,int y,int z){
	nxt[++ecnt]=head[x];head[x]=ecnt;
	e[ecnt].u=x;e[ecnt].v=y;e[ecnt].w=z;
}
inline int getfa(int x){return x==fa[x]?x:fa[x]=getfa(fa[x]);}
inline void dij(){
	priority_queue<pair<int,int> > q;
	memset(d,127/3,sizeof(d));
	q.push(make_pair(0,1));d[1]=0;
	while(!q.empty()){
		int u=q.top().second;q.pop();
		if(vis[u]) continue;
		vis[u]=1;
		for(re int i=head[u];i;i=nxt[i]){
			int v=e[i].v;
			if(d[v]>d[u]+e[i].w){
				d[v]=d[u]+e[i].w;
				tfa[v]=u;
				q.push(make_pair(-d[v],v));
			}
		}
	}
}
int main(){
	n=in;m=in;
	for(re int i=1;i<=m;++i){
		int x,y,z;
		x=in;y=in;z=in;
		add(x,y,z);add(y,x,z);
	}
	dij();
	for(re int i=1;i<=2*m;i+=2){//寻找非树边 
		int u=e[i].u,v=e[i].v;
		if(tfa[u]==v||tfa[v]==u) continue;
		tree[++cnt].w=d[u]+e[i].w+d[v];
		tree[cnt].u=u;
		tree[cnt].v=v;
	}
	sort(tree+1,tree+cnt+1,cmp);
	memset(ans,-1,sizeof(ans));
	for(re int i=1;i<=n;++i) fa[i]=i;
	for(re int i=1;i<=cnt;++i){
		int u=getfa(tree[i].u),v=getfa(tree[i].v);
		while(u!=v){//把这条非树边能够更新的都更新 
			if(d[u]<d[v]) swap(u,v);
			ans[u]=tree[i].w-d[u];
			fa[u]=tfa[u];
			u=getfa(u);
		}
	}
	for(re int i=2;i<=n;++i) printf("%d\n",ans[i]);
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值