bzoj 1576: [Usaco2009 Jan]安全路经Travel(dijkstra堆优化+并查集)

1576: [Usaco2009 Jan]安全路经Travel

Time Limit: 10 Sec   Memory Limit: 64 MB
Submit: 968   Solved: 330
[ Submit][ Status][ Discuss]

Description

Input

* 第一行: 两个空格分开的数, N和M

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

Output

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

Sample Input

4 5
1 2 2
1 3 2
3 4 4
3 2 1
2 4 3

输入解释:

跟题中例子相同

Sample Output

3
3
6

输出解释:

跟题中例子相同

HINT

Source

[ Submit][ Status][ Discuss]



题解:最短路+并查集

用dijkstra的堆优化建立最短路树,并记录最短路上的边,每个节点的父亲节点和节点在树中的深度。

因为最短路树上的1-i路径中的最后一条边是不能通过的,所以就是在最短路树中加一条边,从另一个方向到底当前点

对于一条不在最短路树的边u-nw,长度v,设t=lca(u,nw)

那么对于t-u和t-nw 的路径上所有点x,都可通过先求出环上的长度,在减去dis[x]的长度求得。

路径长度为dis[u]+v+dis[nw]-dis[x],因为dis[x]的长度是固定的,所以最小化这个长度,也就是最小化dis[u]+v+dis[nw]

所以我们可以用这个值去更新t-u,t-nw路径中所有点(不包括t)的最短路长度。

比较好想的思路是树链剖分+线段树,用线段树去维护每个点的dis[u]+v+dis[nw],但是还有更优越的思路。

前面的都不变,只是我们这次不用线段树维护,而是先把所有不再最短路树中的边加到一个结构体中,然后按照 dis[u]+v+dis[nw]从小到大排序,因为权值小的靠前,所有第一次更新的一定是最小的,这样的话每个点至多会被更新一次,那么我们可以把一条链上的点用并查集并到一起,并查集的编号就是树链顶端点的father,这样在求lca 上的路径时,算完一个点就直接蹦到她所属的链顶的上一个(即记录的fa[x]的值),这样每个点最多被计算一次。保证正确性的同时,也保证了科学的时间负责度。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define pa pair<int,int>
#define inf 1000000000
#define N 400003
using namespace std;
int n,m;
int tot,u[N],vis[N],point[N],v[N],next[N],fa[N],pre[N],d[N],mark[N];
int f[N],nw[N],ans[N],num,deep[N],dis[N];
struct data 
{
	int x,y,len;
};data a[N];
void add(int x,int y,int z)
{
	tot++; next[tot]=point[x]; point[x]=tot; u[tot]=y; v[tot]=z; nw[tot]=x;
	tot++; next[tot]=point[y]; point[y]=tot; u[tot]=x; v[tot]=z; nw[tot]=y;
}
int cmp(data a,data b)
{
	return a.len<b.len;
}
void dijkstra()
{
	priority_queue<pa,vector<pa>,greater<pa> > q;
	for (int i=1;i<=n;i++)
	 dis[i]=inf;
	dis[1]=0; deep[1]=1; q.push(make_pair(0,1));
	while(!q.empty())
	{
		int now=q.top().second; q.pop();
		if (vis[now]) continue;
		vis[now]=1;
		for (int i=point[now];i;i=next[i])
		 if (dis[now]+v[i]<dis[u[i]])
		 {
		 	dis[u[i]]=dis[now]+v[i];
		 	mark[pre[u[i]]]=0;
		 	pre[u[i]]=i;  deep[u[i]]=deep[now]+1;
		 	mark[i]=1; f[u[i]]=now;
		 	q.push(make_pair(dis[u[i]],u[i]));
		 }
	}
}
int find(int x)
{
	if (fa[x]==x) return x;
	fa[x]=find(fa[x]);
	return fa[x];
}
void solve(int u,int v,int k)
{
	int x=find(u); int y=find(v);
	while (x!=y)
	{
		if (deep[x]<deep[y]) swap(x,y);
		ans[x]=k-dis[x];
		num++;
		fa[x]=f[x];
		x=find(fa[x]);
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	for (int i=1;i<=m;i++)
	 {
	 	int x,y,z;
	 	scanf("%d%d%d",&x,&y,&z);
	 	add(x,y,z);
	 }
	dijkstra();
	int cnt=0;
	for (int i=1;i<=m;i++)
	 if (!mark[i*2-1]&&!mark[i*2])
	  {
	  	cnt++;
	  	a[cnt].x=nw[i*2];
	  	a[cnt].y=u[i*2];
	  	a[cnt].len=dis[nw[i*2]]+dis[u[i*2]]+v[i*2];
	  }
	sort(a+1,a+cnt+1,cmp);
	for (int i=1;i<=n;i++)
	 ans[i]=-1,fa[i]=i;
	for (int i=1;i<=cnt;i++)
	{
	 solve(a[i].x,a[i].y,a[i].len);
	 if (num==n-1) break;
    }
    for (int i=2;i<=n;i++)
     printf("%d\n",ans[i]);
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值