题目:
给出一个 N 个顶点 M 条边的无向无权图,顶点编号为1∼N。问从顶点 1 开始,到其他每个点的最短路有几条。(1≤N≤10^6,1≤M≤2×10^6)
思路:
- 构图:由题意得,这是一个无向图,且N(顶点的数量)和M(边的数量)的数据范围很大。这里可以将一条无向边拆成两条有向边(如:x,y之间有一条边,储存为x到y有一条边,y到x有一条边),用f数组(f[i]表示顶点i关联的最新的一条边)和结构体b数组(from,to,next)储存。
- 寻找最短路;无权图求最短路问题—>储存dl数组的点,层层遍历,更新dl数组的点
- 记录最短路:ans[i]代表 到达i点的最短路的数量,那么若由i点遍历到j点,说明i成为j的某一条最短路上的必经点,ans[j]=ans[j]+ans[i](AT:mod100003)
- 解决自环与重边问题:自环—>用bj数组标记,“最短路”上不会走回头路;重边—>重边是要被重复计算的,也就是点j的最短路可以由dl数组中多个点抵达,同时也可以由i点(dl中的点)的多条边抵达,只要调整bj[j]=1的位置以及引入“临时标记”dj即可。(详情见代码及注释)
代码展示:
#include<stdio.h>
#include<stdlib.h>
struct line
{
int from,to;
int next;
};
struct line b[4000005];
int f[1000005];//f[i]-点i关联的最新的边
//抓住一个关键:由于该图的边没有权值,所以将点一层层遍历就可以找到最短路(从点1开始经过最少的点抵达)
//解决自环:bj数组,不走回头路
//解决重边问题:dj 在同一层点里重复更新
int bj[1000005];
int dj[1000005];
int dl[1000005];int js; //储存当前层的点 js记录dl中元素数量
int dl1[100005];int jjs;//为了更新下一层的点 jjs记录dl1中元素数量
int ans[100005];
int main()
{
int N,M;int i,j,t;int x,y;
scanf("%d%d",&N,&M);
//搭建2*M条有向边
for(i=1;i<=M;i++)
{
scanf("%d %d",&x,&y);
b[i].from=x;b[i].to=y;
b[i].next=f[x];f[x]=i;
}
for(i+1;i<=2*M;i++)
{
x=b[i-M].to;y=b[i-M].from;
b[i].from=x;b[i].to=y;
b[i].next=f[x];f[x]=i;
}
dl[1]=1;bj[1]=1;js=1;//第一层的点只有顶点1
ans[1]=1;
while(1)
{
jjs=0;
for(i=1;i<=js;i++)//遍历当前层(dl)中的点,寻找下一层(dl1)中的点
{
int na=dl[i];
for(j=f[na];j!=0;j=b[j].next)//遍历当前层(dl)关联的所有边,找下一层(dl1)的点
{
int na1=b[j].to;
if(bj[na1]==0)//na1经过na可以抵达最短路
{
ans[na1]+=ans[na];
ans[na1]=ans[na1]%100003;
if(dj[na1]==0)//若na1不在dl1数组里
{
jjs++;dl1[jjs]=na1;
}
dj[na1]=1;//na1进入了dl1数组
}
}
}
//用队列1数组更新dl数组
for(i=1;i<=jjs;i++)
{
dl[i]=dl1[i];
bj[dl[i]]=1;//标记 不走回头路
}
if(jjs==0) break;//找不到下一层了-这是最后一层
js=jjs;
}
for(i=1;i<=N;i++)
{
printf("%d",ans[i]);
if(i!=N) printf("\n");
}
return 0;
}