洛谷 1144 最短路计数(无权图最短路)

题目:

给出一个 N 个顶点 M 条边的无向无权图,顶点编号为1∼N。问从顶点 1 开始,到其他每个点的最短路有几条。(1≤N≤10^6,1≤M≤2×10^6)

 

思路:

  1. 构图:由题意得,这是一个无向图,且N(顶点的数量)和M(边的数量)的数据范围很大。这里可以将一条无向边拆成两条有向边(如:x,y之间有一条边,储存为x到y有一条边,y到x有一条边),用f数组(f[i]表示顶点i关联的最新的一条边)和结构体b数组(from,to,next)储存。
  2. 寻找最短路;无权图求最短路问题—>储存dl数组的点,层层遍历,更新dl数组的点
  3. 记录最短路:ans[i]代表 到达i点的最短路的数量,那么若由i点遍历到j点,说明i成为j的某一条最短路上的必经点,ans[j]=ans[j]+ans[i](AT:mod100003)
  4. 解决自环与重边问题:自环—>用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;
}

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值