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;
}