题目地址: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。
数据范围
1≤N≤1e5
1≤M≤2e5
题解:最短路问题最常见的是求最短路的权值,这道题让求的是最短路的条数。对于这类问题,也有很多的求解方法。
我们可以求出最短路的一个拓扑图,首先利用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