P1608 路径统计(带权图的最短路数统计,dijkstra算法)

我可以这么说:spfa不仅仅是bellman_ford的队列优化版本,而且是魔改!

P1608 路径统计

题目描述

给定一张有向有权图,求出点1到点n的最短路长度和最短路的数量. 可能无解,但是不会出现负权边和自环,若有重边请忽略影响,只算一条.

题目分析

之前我们有做过无权图的最短路算法,那时候我们直接对 s p f a spfa spfa进行操作就好了.

然鹅这道题, s p f a spfa spfa就不能直接操作,但是 d i j k s t r a dijkstra dijkstra就可以.

为什么呢?我们不妨从算法的角度出发.

我们知道,dij基于的思想是贪心,每次我们从起点每次新拓展一个到起点距离最短的点,再以这个点为中间点,更新起点到其他所有点的距离.

所以已经被操作的这个“当前到起点距离最短的点”,是不会再一次被操作的,

也就是说,每个点都只会在 p r i o r i t y   q u e u e priority\:queue priorityqueue中操作一次,在它被操作前最后一次更新后dis和sum一定是固定的.

但是 s p f a spfa spfa算法不一样,它并没有所谓“某次更新后就不会改变”的情况, s p f a spfa spfa的最短路和最短路数在 q u e u e queue queue为空之前一直有被更新的可能.

那么为什么在无权图上, s p f a spfa spfa算法成立呢?那是因为 s p f a spfa spfa算法同时基于三角形性质:

dis[u]+e[u][v]>=dis[v],其中dis[u],dis[v]为已经确定的最短路.

我们发现,在无权图上,不等式左边始终大于右边,而在有权图上,这是不一定的.

故而在有权图上,一个点可能进入 q u e u e queue queue多次,从而受到前一个节点的影响,导致重复计算等一系列不可预知的问题.

这也是我为什么说“魔改”,就是因为它的 q u e u e queue queue进入顺序实在太不可确定了!

那有没有解决方法呢?也是有的,只要消除了前面的节点的影响就行.

1.只有当一个点到起点的路径数不为0且未入队时,我们才将它放入队列.
2.如果搜索到的一条边的终点为n,那么我们应该跳过这一条边的搜索,而不是直接结束程序.(why???)
3.每次对取出来的点进行完搜索后,一定要将它的路径数清0,不然会出现重复统计.(这是为了满足1,实现再次进队)

总而言之,很麻烦的样子。。。而且不是很能理解. 若是要求所有节点的最短路数,也不合适.

总之还是挂篇 s p f a spfa spfa题解

所以以后遇上还是老老实实写dij吧,毕竟dij中更新是有穷的,可预知确定的操作简单,而 s p f a spfa spfa不是.

程序实现

#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
#define maxn 2010
using namespace std;
struct edge{
	int v,w,next;
}e[maxn*maxn];
int head[maxn],tot;
void add(int u,int v,int w){
	e[++tot].v =v;
	e[tot].w =w;
	e[tot].next=head[u];
	head[u]=tot;
}
int ed[maxn][maxn];
struct node{
	int dis,pos;
	bool operator <(node nd)const{
		return dis>nd.dis ;
	}
};//好像经常容易忘记这个分号
priority_queue<node> q;
bool vis[maxn];
int dis[maxn],sum[maxn];
void dijkstra(int s,int t){
	memset(dis,inf,sizeof dis);
	q.push((node){0,1});
	dis[1]=0;
	sum[1]=1;
	while(!q.empty()){
		node now=q.top();
		q.pop();
		int u=now.pos ;
		if(vis[u])continue;
		vis[u]=true;//注意vis表示最短路已确定的点
		for(int i=head[u];i;i=e[i].next ){
			int v=e[i].v ;
			if(dis[v]>dis[u]+e[i].w ){
				dis[v]=dis[u]+e[i].w ;
				sum[v]=sum[u];//核心操作
				if(!vis[v])q.push((node){dis[v],v});
			}
			else if(dis[v]==dis[u]+e[i].w )sum[v]+=sum[u];//
		}
	}
}
int n,m;
int main(){
	memset(ed,inf,sizeof ed);
	scanf("%d%d",&n,&m);
	for(int i=1,u,v,w;i<=m;i++){
		scanf("%d%d%d",&u,&v,&w);
		if(ed[u][v]>w)add(u,v,w),ed[u][v]=w;//防重边(完全一样的边)
	}
	dijkstra(1,n);
	if(dis[n]==inf)printf("No answer\n");//特判
	else printf("%d %d\n",dis[n],sum[n]);
	return 0;
}

题后总结

对基本算法的理解要足够透彻,否则会在需要灵活使用时不知所措.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值