1. 程式人生 > 實用技巧 >AcWing1134最短路計數(spfa)

AcWing1134最短路計數(spfa)

題目地址https://www.acwing.com/problem/content/1136/

題目描述

給出一個N個頂點MM條邊的無向無權圖,頂點編號為11到NN。

問從頂點1開始,到其他每個點的最短路有幾條。

輸入格式

第一行包含2個正整數N,M,為圖的頂點數與邊數。

接下來M行,每行兩個正整數x,y,表示有一條頂點x連向頂點y的邊,請注意可能有自環與重邊。

輸出格式

輸出N行,每行一個非負整數,第i行輸出從頂點1到頂點i有多少條不同的最短路,由於答案有可能會很大,你只需要輸出對100003取模後的結果即可。

如果無法到達頂點i則輸出0

資料範圍

1N1e5
1M2e5

題解:最短路問題最常見的是求最短路的權值,這道題讓求的是最短路的條數。對於這類問題,也有很多的求解方法。

我們可以求出最短路的一個拓撲圖,首先利用dijsktra求出所有點,不過這裡求的是,如果結點j的最短路是結點i,那麼就儲存下i是j的前驅,一個結點可能會有多條前驅,需要全部記下來,那麼這樣儲存的就是一個新的無環的拓撲圖,然後直接從源點s進行bfs就可以了,對於從佇列中出來的結點,出來幾次,那麼到該結點的最短路就有幾條。

當然,上面的這個方法,首先需要保證不存在負圈,並且也不能存在權值為0的環。

另一個辦法就是利用spfa的鬆弛操作。如果隊列當前拿出的結點為now,那麼遍歷now的每條邊,如果邊的另一個結點為v;dis[v]<=dis[now],那麼結點v不能更新v,就忽略;dis[v]==dis[now]+1,那麼這條路也是當前(注意這裡不一定是全域性)最短路,讓num[v]+=num[now](num[i]表示當前到結點i的最短路的條數);dis[v]>dis[now]+1,那麼結點v的最短路就會被更新,讓num[v]=num[now]即可。

AC程式碼:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
const int N=1e5+10,M=2e5+10,mod=1e5+3;
typedef pair<int,int> PII;
struct node{
    int from,to,next;
}edge[M*2];
int head[N],cnt,dis[N],num[N];
int n,m;

void addedge(int u,int
v){ cnt++; edge[cnt].from=u; edge[cnt].to=v; edge[cnt].next=head[u]; head[u]=cnt; } void spfa(int s){ int st[N]={0}; dis[1]=0; num[s]=1; queue<int>q; q.push(s); st[s]=1; while(!q.empty()){ int now=q.front();q.pop(); st[now]=0; for(int i=head[now];~i;i=edge[i].next){ if(dis[edge[i].to]<=dis[now]) continue; if(dis[edge[i].to]==dis[now]+1) num[edge[i].to]+=num[now]; else num[edge[i].to]=num[now]; num[edge[i].to]%=mod; dis[edge[i].to]=dis[now]+1; if(st[edge[i].to]) continue; q.push(edge[i].to); st[edge[i].to]=1; } } } int main(){ memset(head,-1,sizeof(head)); memset(num,0,sizeof(num)); memset(dis,0x3f,sizeof(dis)); cin>>n>>m; for(int i=1,u,v;i<=m;i++){ scanf("%d%d",&u,&v); addedge(u,v); addedge(v,u); } spfa(1); for(int i=1;i<=n;i++) cout<<num[i]<<endl; return 0; }

寫於:2020/9/10 15:52