洛谷 P2832 行路难

题面

    这个最短路有点special,会有疲劳度的加成效应,这个时候应该怎么办呢?

    难就难在,如果走一条路比另一条路长,但是用的边少,那么这条路并不一定就更差。

    我们要是能解决这个问题,就可以做出本题。

    

    想一想两种常用的单源最短路的实现过程,dij是优先队列每次弹出非标记点中最近的那个,而spfa则可以看成bfs的延伸,用的边少的一定是会比用的边多的先被扩展到的。

    如果这么一想,那么选择就很显然了:我们如果用spfa的话,顺带记录一下疲劳值,是一定能找出答案的。我们考虑spfa的过程,如果 d[x](目前要扩展的点)+ val[i] (边权)+pl(队列里这个点的疲劳值)>= d[to](边的出点),那么这条路径一定是无用的,因为这样走到to的路径长度不仅没变短,反而疲劳值更大(注意spfa的队列里的疲劳值肯定是单调不减的),所以肯定不会是答案,而spfa的过程也是不允许这种情况再入队列的;相反的,如果d[x] + val[i] + pl< d[to],那么说明这条路径还有可能是答案,我们把它加到队列里去,但这并不会影响之前已经入队列的疲劳值更小的状态的寻找答案的过程。

 

    最后就是打印最短路的过程了,显然不能像以前一样开个p[i]记录最短路树的上一个节点,因为这种特殊的spfa求出的根本就不是一颗树(其实普通最短路也不一定是一棵树,不过很多边是等价的于是我们可以把等价的边删的只剩一条就可以构建最短路树)!而是一个复杂的DAG!

    但如果我们记录队列里的每个状态是由哪个状态转移过来的,答案就可以轻松找到了,具体操作可以看代码、、、

 

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=10005,M=200005,inf=2e9;

inline int read(){
    int x=0; char ch=getchar();
    for(;!isdigit(ch);ch=getchar());
    for(;isdigit(ch);ch=getchar()) x=x*10+ch-'0';
    return x;
}

int to[M*2],ne[M*2],hd[N],val[M*2],D[N];
int ans=inf,P,num,n,m,l,r;
struct node{
	int x,pl,pr,d;
}q[10000005];

inline void add(int x,int y,int z){
	to[++num]=y,ne[num]=hd[x],hd[x]=num,val[num]=z;
}

inline void solve(){
	q[l=r=1]=(node){1,0,0,0},D[1]=0;
	
	for(node now;l<=r;l++){
		now=q[l];
		if(now.x==n&&now.d<ans) ans=now.d,P=l;
		
		for(int i=hd[now.x],dd;i;i=ne[i]) if((dd=now.d+now.pl+val[i])<D[to[i]])
			D[to[i]]=dd,q[++r]=(node){to[i],now.pl+1,l,dd};
	}
}

void Print(int x){
	if(!x) return;
	Print(q[x].pr);
	printf("%d ",q[x].x);
}

int main(){
	n=read(),m=read(),fill(D+1,D+n+1,inf);
	for(int u,v,w;m;m--) u=read(),v=read(),w=read(),add(u,v,w);
	solve(),printf("%d\n",ans);
	Print(P);
	return 0;
}

  

 

posted @ 2019-08-03 16:50  蒟蒻JHY  阅读(166)  评论(0编辑  收藏  举报