P1608 路徑統計(帶權圖的最短路數統計,dijkstra算法)

我可以這麼說:spfa不僅僅是bellman_ford的隊列優化版本,而且是魔改!

P1608 路徑統計

題目描述

給定一張有向有權圖,求出點1到點n的最短路長度和最短路的數量. 可能無解,但是不會出現負權邊和自環,若有重邊請忽略影響,只算一條.

題目分析

之前我們有做過無權圖的最短路算法,那時候我們直接對spfaspfa進行操作就好了.

然鵝這道題,spfaspfa就不能直接操作,但是dijkstradijkstra就可以.

爲什麼呢?我們不妨從算法的角度出發.

我們知道,dij基於的思想是貪心,每次我們從起點每次新拓展一個到起點距離最短的點,再以這個點爲中間點,更新起點到其他所有點的距離.

所以已經被操作的這個“當前到起點距離最短的點”,是不會再一次被操作的,

也就是說,每個點都只會在priority queuepriority\:queue中操作一次,在它被操作前最後一次更新後dis和sum一定是固定的.

但是spfaspfa算法不一樣,它並沒有所謂“某次更新後就不會改變”的情況,spfaspfa的最短路和最短路數在queuequeue爲空之前一直有被更新的可能.

那麼爲什麼在無權圖上,spfaspfa算法成立呢?那是因爲spfaspfa算法同時基於三角形性質:

dis[u]+e[u][v]>=dis[v],其中dis[u],dis[v]爲已經確定的最短路.

我們發現,在無權圖上,不等式左邊始終大於右邊,而在有權圖上,這是不一定的.

故而在有權圖上,一個點可能進入queuequeue多次,從而受到前一個節點的影響,導致重複計算等一系列不可預知的問題.

這也是我爲什麼說“魔改”,就是因爲它的queuequeue進入順序實在太不可確定了!

那有沒有解決方法呢?也是有的,只要消除了前面的節點的影響就行.

1.只有當一個點到起點的路徑數不爲0且未入隊時,我們纔將它放入隊列.
2.如果搜索到的一條邊的終點爲n,那麼我們應該跳過這一條邊的搜索,而不是直接結束程序.(why???)
3.每次對取出來的點進行完搜索後,一定要將它的路徑數清0,不然會出現重複統計.(這是爲了滿足1,實現再次進隊)

總而言之,很麻煩的樣子。。。而且不是很能理解. 若是要求所有節點的最短路數,也不合適.

總之還是掛篇spfaspfa題解

所以以後遇上還是老老實實寫dij吧,畢竟dij中更新是有窮的,可預知確定的操作簡單,而spfaspfa不是.

程序實現

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

題後總結

對基本算法的理解要足夠透徹,否則會在需要靈活使用時不知所措.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章