【USACO 2009 JAN GOLD】安全路径

Description

Gremlins最近在农场上泛滥,它们经常会阻止牛们从农庄(牛棚_1)走到别的牛棚(牛_i的目的地是牛棚_i)。每一个gremlin只认识牛_i并且知道牛_i一般走到牛棚_i的最短路经。所以它们在牛_i到牛棚_i之前的最后一条牛路上等牛_i,当然,牛不愿意遇到Gremlins,所以准备找一条稍微不同的路经从牛棚_1走到牛棚_i,所以,请你为每一头牛_i找出避免gremlin_i的最短路经的长度。 

和以往一样,农场上的M (2 <= M <= 200,000)条双向牛路编号为1..M并且能让所有牛到达它们的目的地,N(3 <= N <= 100,000)个编号为1..N的牛棚。牛路i连接牛棚a_i (1 <= a_i <= N)和b_i (1 <= b_i <= N)并且需要时间t_i (1 <=t_i <= 1,000)通过。没有两条牛路连接同样的牛棚,所有牛路满足a_i!=b_i。在所有数据中,牛_i使用的牛棚_1到牛棚_i的最短路经是唯一的。 
以下是一个牛棚,牛路和时间的例子: 

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

【数据范围】 
20%的数据满足,N<=200; 
50%的数据满足,N<=3000 
100%的数据满足,N<=100,000 


【分析】

算法:Dijkstra(加Heap)+左偏树

本题大意:给定起点,求不经过最短路中父边的最短路。

删边暴力做肯定是会TLE的。

仔细阅读题目后,我们发现原图的最短路是唯一的。而最短路可以用一棵树来表示,这就意味着,这棵树是唯一的。这就给了我们很大的启发。

我们以起点为根,遍历这棵树。

为方便叙述,我们先定义几个数组:dis[i]表示原图起点到i点的最短路,fa[i]表示最短路中i号节点的父点,son[i]表示i的子孙集合。

那么对于编号为u的节点,它的答案有两种情况:

1.由某个点v,直接连一条边到u。这时路的长度为dis[v]+w[v][u]

注意这里v的要求为:v!=u,v!=son[u],v!=fa[u]。

这三个条件是很显然的,因为我们要保证dis[v]没有经过u的父边。

2.由某个点v,直接连一条边到u的某个子孙p,然后由p走到u。这时路的长度为dis[v]+w[v][p]+dis[p]-dis[u]

注意这里v的要求为:v!=u,v!=son[i] (这里v可以为fa[u])。

那么,再来考虑代码的实现:

对于情况一,我们可以直接枚举与u相连的每条边(因为是双向边),然后求得情况一的答案。

而难点就在于情况二的解决。仔细观察上面两个加粗的式子,我们发现:

u号点的情况二,其实可以由其子孙节点的情况一继承过来。

这样便大大减少了时间复杂度。我们对每个节点建立一颗左偏树(因为要合并,而且要取最小值),然后每次从左偏树中取出根元素,求得当前节点的情况二,注意如果根元素不满足上述情况二的要求,那么删除后再取根元素。并枚举求得情况一,然后添加进左偏树,为父节点的答案做准备。

有一些细节要注意:这道题比较卡,一是卡最短路,要用迪杰斯特拉加堆,二是卡递归,要用手工栈。


【代码】

/*
    ID:Ciocio
	LANG:C++
	DATE:2013-12-06
	TASK:Saferoad
*/
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <queue>
#include <stack>
#include <utility>
#include <functional>
#include <vector>
#include <map>

using namespace std;

#define MAXM 800005
#define MAXN 200005
#define INF 999999999
#define m_p(x,y) make_pair(x,y)

typedef pair<int,int> pii;
int N,M,cnt;
int Y[MAXM],W[MAXM],Last[MAXM],Next[MAXM];
int dis[MAXN],fa[MAXN],gr[MAXN];
int Ls[MAXM],Rs[MAXM],Key[MAXM],ano[MAXM],dep[MAXM];
int beg[MAXN],end[MAXN],root[MAXN],Ans[MAXN];
//为方便判断某个点是否为当前节点的子孙,给节点重新编号
//gr[]表示重新编号后的号
//beg[],end[]分别表示子孙节点的范围下界和上界
bool vis[MAXN];

void _read(int &x)
{
	char tt=getchar();
	while(tt<'0'||'9'<tt) tt=getchar();
	for(x=0;'0'<=tt&&tt<='9';x=x*10+tt-'0',tt=getchar());
}

int tot;
void _addedge(int a,int b,int c)
{
	tot++;
	Y[tot]=b;W[tot]=c;
	Next[tot]=Last[a];
	Last[a]=tot;
}

void _init()
{
	_read(N);_read(M);
	int a,b,t;
	for(int i=1;i<=M;i++)
	{
		_read(a);_read(b);_read(t);
		_addedge(a,b,t);
		_addedge(b,a,t);
	}
}

priority_queue<pii,vector<pii>,greater<pii> >PQ;
void _Dijkstra()
{
	memset(vis,0,sizeof(vis));
	while(!PQ.empty()) PQ.pop();
	for(int i=1;i<=N;i++) dis[i]=INF;
	dis[1]=0;PQ.push(m_p(dis[1],1));
	while(!PQ.empty())
	{
		int u=PQ.top().second;PQ.pop();
		if(vis[u]) continue;
		vis[u]=true;
		for(int j=Last[u];j;j=Next[j])
		{
			int v=Y[j];
			if((!vis[v])&&(dis[v]>dis[u]+W[j]))
			{
				dis[v]=dis[u]+W[j];
				fa[v]=u;              //记录父点
				PQ.push(m_p(dis[v],v));
			}
		}
	}
}

int _merge(int x,int y)  //左偏树合并操作
{
	if(!x) return y;
	if(!y) return x;
	if(Key[x]>Key[y]) 
		swap(x,y);
	Rs[x]=_merge(Rs[x],y);
	if(dep[Rs[x]]>dep[Ls[x]])
		swap(Rs[x],Ls[x]);
	dep[x]=dep[Rs[x]]+1;
	return x;
}

stack <int> S;
int Con[MAXN];        
void _DFS(int u)         
{
	memset(vis,0,sizeof(vis));
	while(!S.empty()) S.pop();
	S.push(u);
	while(!S.empty())
	{
		u=S.top();
		if(!vis[u]) {vis[u]=true;gr[u]=++cnt;beg[u]=cnt;}
		for(int j=Con[u]?Con[u]:Last[u];j;j=Next[j])
		{
			int v=Y[j];
			if(fa[v]==u)      //如果这条边是最短路树上的
			{
				if(!vis[v])  //若未被访问,压入栈
				{
				    Con[u]=j;
				    S.push(v);
					goto END;
				}
				else          //已被访问,合并左偏树
					root[u]=_merge(root[u],root[v]);
			}
			else if(v!=fa[u])  
			 	//若不是最短路树上的,符合情况一,可以用来更新当前节点,放入左偏树
			{
				Key[j]=dis[u]+dis[v]+W[j];    
				    //由于边的编号是唯一的,我们用边的编号j来给左偏树节点编号
				ano[j]=v;     //ano[]记录由哪个节点更新的当前值,以判断是否符合要求
				root[u]=_merge(root[u],j);
			}
		}
		end[u]=cnt;
	    for(int j=root[u];j&&beg[u]<=gr[ano[j]]&&gr[ano[j]]<=end[u];j=root[u])
		    root[u]=_merge(Rs[j],Ls[j]);
		    //删除不满足情况一和二要求的节点
	    if(root[u]) 
			Ans[u]=Key[root[u]]-dis[u];  //更新答案
		S.pop();
		END:continue;
	}
}

void _solve()
{
	_Dijkstra();
	for(int i=1;i<=N;i++)
		Ans[i]=-1;
	dep[0]=-1;
	_DFS(1);
	for(int i=2;i<=N;i++)
		printf("%d\n",Ans[i]);
}

int main()
{
	_init();
	_solve();
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值