最短路计数

题目描述

给出一个N个顶点M条边的无向无权图,顶点编号为1−N。问从顶点1开始,到其他每个点的最短路有几条。

输入格式

第一行包含2个正整数N,M,为图的顶点数与边数。

接下来M行,每行2个正整数x,y,表示有一条顶点x连向顶点y的边,请注意可能有自环与重边

输出格式

共N行,每行一个非负整数,第i行输出从顶点1到顶点i有多少条不同的最短路,由于答案有可能会很大,你只需要输出ans mod 100003后的结果即可。如果无法到达顶点i则输出0

用到的知识点

1、广度优先搜索

2、vector容器

开成一个vector数组,其实也可以用邻接表即链表的形式来储存数据

3、存储顶点深度的数组

dep[maxm]

4、存储顶点是否访问过的数组

vis[maxm]

5、储存最短路径条数的数组

cnt[maxn]:值得品味

6、循环条件为队列不为0

7、思考

可以使用一个类来表示顶点,类中的数据成员为顶点标识、顶点深度、是否被访问过;
再使用一个链表数组来代替vector类,具体操作方式与vector类相似;
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<queue>
using namespace std;
//广度优先搜素默认就是最短路径搜索

const int maxn = 1000000 + 1, maxm = 2000000 + 1, INF = 0x7f7f7f7f, MOD = 100003;
vector<int>G[maxn];             //这是一个vector数组
int dep[maxn]; 
bool vis[maxn];                 //其中的数据都置为0
int cnt[maxn];

int main() {
	int N, M; scanf("%d%d", &N, &M);
	for (int i = 1; i <= M; i++) {
		int x, y; scanf("%d%d", &x, &y);
		G[x].push_back(y);     //第x个顶点与第y个顶点相连(在这里面可以存放多个顶点)
		G[y].push_back(x);     //第y个顶点与第x个顶点相连
	}
	queue<int>Q;               //创建队列
	dep[1] = 0; 
	vis[1] = 1; 
	Q.push(1); 
	cnt[1] = 1;
	while (!Q.empty()) {      //判断队列是否为空
		int x = Q.front();    //取出队列中最前面的元素
		Q.pop();              //删除最前面的元素
		for (int i = 0; i < G[x].size(); i++) {
			int t = G[x][i];                //取出其中的一个顶点
			if (!vis[t]) {                  //对于访问过的顶点不再操作
				vis[t] = 1;                 
				dep[t] = dep[x] + 1;        //取出顶点的深度等于原来顶点深度+1 
				Q.push(t);                  //将该顶点挤入队列中等待被读取
			}
			if (dep[t] == dep[x] + 1) { 
				cnt[t] = (cnt[t] + cnt[x]) % MOD;            //这里要算的是条数(cnt【i】保存的是到i顶点的最短路径数)
			}
		}
	}
	for (int i = 1; i <= N; i++) {
		printf("%d\n", cnt[i]);
	}
	return 0;
}
<think>好的,我现在要解决的是P1144短路计数的问题,需要用Java来实现。这个问题应该是关于在无权图中找出从起点到各个点的最短路径的数量。首先,我需要仔细理解题目要求,然后考虑如何用Java代码实现。 首先,题目大意是给定一个无向无权图,可能存在自环和重边。需要计算从节点1出发到每个点的最短路径的数量,结果对100003取模。因为是无权图,所以每条边的权重可以看作1,这样最短路径可以通过广度优先搜索(BFS)来找到。每次遇到更短的路径时更新计数,如果路径长度相同,就将计数累加。 那接下来,我需要考虑如何实现这个算法。因为是无权图,BFS是合适的选择。因为BFS逐层扩展,先到达的节点路径长度短。当访问某个节点时,所有未访问的邻居的最短路径长度是当前节点的长度加一。如果已经被访问过,但路径长度相同,说明有多个路径到达该邻居,需要将当前节点的计数加到邻居的计数上。 具体步骤可能是这样的: 1. 初始化一个距离数组dist,初始值为无穷大,起点dist[1] = 0。 2. 初始化一个计数数组cnt,cnt[1] = 1,其他初始为0。 3. 使用队列进BFS,先将起点1入队。 4. 每次取出队列中的节点u,遍历其所有邻居v。 5. 如果dist[v]未被访问过(即dist[v]是无穷大),则更新dist[v]为dist[u]+1,并将cnt[v]设为cnt[u]。然后将v入队。 6. 如果dist[v]已经被访问,且dist[v] == dist[u]+1,说明这是另一条最短路径,需要将cnt[v] += cnt[u],并取模。 7. 自环的情况需要处理吗?因为题目允许自环,但是自环的话,u到u的边在BFS中不会被处理,因为当处理u的时候,检查到v是u时,此时dist[u]已经是短距离,而加上1之后是dist[u]+1,只有当dist[u]+1等于dist[u]的时候才会累加,但这只有在边权为0的时候才可能。但这里边权是1,所以自环的情况不会影响最短路径计数。所以可以忽略自环的影响,或者在遍历的时候不管,不影响结果。 8. 重边的话,比如节点u和v之间有多个边,这在BFS中会被视为多次邻居遍历,但每次处理邻居时,如果是同一层的话,会多次累加cnt[u]。例如,如果有两条边u->v,那么第一次处理v的时候,cnt[v] = cnt[u],第二次可能再次处理的时候,如果此时v已经被访问且距离相同,那么cnt[v]会增加两次cnt[u]。这时候需要正确的处理,所以重边的情况会被自动处理,因为每次遍历到v的时候都会检查是否路径长度相同。 接下来,如何构建图?因为节点数可能很大,比如1e5,所以用邻接表比较合适。Java中可以用ArrayList数组或者List<List<Integer>>。每个节点对应的邻接点列表。 然后,处理输入。根据题目输入,第一是n和m,然后m两个整数代表边。因为是无向图,所以每条边要添加两次。 接下来,初始化dist数组为无穷大,可以用一个整数数组,初始值设为-1或者一个很大的数。cnt数组初始化为0,除了起点是1。 队列的话,可以用LinkedList实现队列。Java中的Queue接口,用LinkedList实现。 具体步骤: - 读取n和m。 - 构建邻接表。 - 初始化dist和cnt数组。 - BFS遍历。 - 输出cnt数组中的每个元素。 需要注意的点: - 节点编号是从1到n。 - 结果要对100003取模。 - 处理重边时,可能会有多次访问同一个节点的情况,比如同一层的不同边导致多次处理,这时候需要正确累加计数。 - 大数组的处理,比如n到1e5的话,邻接表要用足够大的容量,避免越界。 现在想一个例子,比如样例输入: 4 5 1 2 1 3 2 4 3 4 3 4 对应的图是: 1连接2和3。2连接4,3连接4两次。那么从1到4的最短路径是经过2或者3,每条长度是2。总共有11->2->4) + 11->3->4) + 11->3->4)?或者要看具体路径数量。或者原题样例的输出是到4的最短路径数目是3? 原题样例的输出可能为: 111 到2:1 到3:1 到4:2+1=3?或者可能根据边的情况不同。例如,题目中的第三个边是2-4,第四、五条边是3-4两次。所以从1到3的最短路径数目是1。然后3到4有两条边,所以当处理到3的时候,对于4的第一次访问,距离是2,此时cnt[4] += cnt[3](即1),之后遇到另一条边3-4的时候,此时4已经被访问过,且距离是2,所以再次加上cnt[3],也就是1。所以总共有11->2->4) + 2(两次1->3->4) = 3?或者原题样例的输出可能不同。比如原题给出的样例输出为: 1 1 1 3 所以这说明到节点4的最短路径数目是3。这种情况下,当处理节点2时,将4的距离设置为2,cnt[4] +=1。处理节点3时,当第一次处理到4时,发现距离是无穷大,设置距离为2,cnt[4]=1(因为节点3的cnt是1)。然后第二次处理到4的边(第三个边是3-4),此时4已经被访问,距离是2等于当前节点的距离+1(节点3的dist是1+1=2),所以cnt[4] +=1,此时变为2。然后可能还有另一条边3-4的情况,第三次处理的话,同样cnt[4]再增加1,变成3。所以总共有三个路径1-2-4,1-3-4(两次边)。 这说明在邻接表中,每个边都要处理。所以,在邻接表中,对于节点3,邻接点列表包含4出现两次。这样在遍历时,处理这两个4的时候,都会触发cnt的累加。 所以在构建邻接表的时候,必须将每条边都加入,包括重边。这样在BFS处理时,会多次遍历到同一个邻居节点,从而正确累加计数。 因此,邻接表的正确构建很重要。 现在,考虑如何编写Java代码: - 输入处理:使用Scanner或者BufferedReader。因为节点数可能很大,比如1e5,所以用BufferedReader更高效。 - 邻接表的初始化:List<List<Integer>> adj = new ArrayList<>(); 然后对于每个节点,初始化一个ArrayList。注意节点编号从1到n,所以数组大小要是n+1。 - BFS的队列:使用LinkedList作为队列。 - dist数组初始为-1,cnt初始为0。起点dist[1] =0,cnt[1]=1。 - BFS过程中,取出节点u,遍历其所有邻居v。 - 对于每个v,如果dist[v]==-1,说明第一次访问,设置dist[v]=dist[u]+1,cnt[v] = cnt[u],然后将v入队。 - 如果dist[v] == dist[u]+1,说明之前已经访问过,但路径长度相同,所以cnt[v] += cnt[u],然后取模。 - 需要处理多次入队的情况吗?例如,同一个节点可能被多次加入队列?比如,如果有多个路径到达同一节点,但只有当第一次发现最短路径时才入队。之后的访问都是在找到相同距离的情况下,只是更新计数,不需要再次入队。因为BFS的性质,当处理到u时,v的短距离已经被确定,之后出现的相同距离的情况不会改变短距离,所以不需要再次入队。例如,当处理节点u时,如果v已经被访问过且距离等于u的距离+1,那么只需要更新cnt[v]即可,不需要再次将v加入队列。所以,在代码中,当第一次发现v时,设置距离,并入队。之后遇到v的其他边时,如果距离相同,则只增加cnt,但不入队。 所以代码的大体结构如下: 读取n和m. 构建邻接表,每个节点的邻接列表。 初始化dist数组为-1,cnt数组为0. dist[1] =0; cnt[1] =1; 队列加入节点1. 然后,队列不为空时,取出节点u。 遍历u的所有邻居v. 如果dist[v] ==-1: dist[v] = dist[u]+1; cnt[v] = cnt[u] % MOD; 将v加入队列. 否则 if (dist[v] == dist[u]+1): cnt[v] = (cnt[v] + cnt[u]) % MOD; 处理完成后,输出每个节点的cnt[i]. 这样是否正确? 假设是的。那这样的话,比如对于节点3的两个边到4的情况: 当处理节点3的时候,第一次处理到4,发现dist[4]是-1,于是设置dist[4]为2,cnt[4]为1(节点3的cnt是1),然后将4入队。第二次处理到4(因为邻接表中有两个边3-4),此时dist[4]已经是2,而dist[u]+1=1+1=2,所以进入第二个条件,cnt[4] +=1,变为2。第三次可能还有另一个边,比如第三个边,那么同样的情况,cnt[4]再加1,变为3。这样终得到cnt[4]为3,符合样例。 所以代码需要正确地将所有邻接边处理,不管是否有重复。 现在,如何处理节点数目很大,比如1e5的情况?这时候Java中的数据结构是否足够高效? 邻接表用List<List<Integer>>,每个节点对应的邻接列表用ArrayList初始化,这样在添加边时是O(1)的时间。总的构建时间是O(m)。BFS的时间是O(n + m),因为每个节点被处理一次,每条边被处理两次(因为是双向的),但对于BFS来说,每个边被处理两次,但每个节点只入队一次。所以整个算法的时间复杂度是可接受的,对于1e5的节点和2e5的边来说,Java应该可以处理。 然后,关于MOD的问题,题目要求结果对100003取模。所以每次更新cnt的时候都要取模,包括初始化和累加的时候。 接下来,编写代码的步骤: 1. 读取输入。使用BufferedReader读取n和m。 需要注意,可能有多余的空格或者换符,需要用split分割。 例如: BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String[] firstLine = br.readLine().split(" "); int n = Integer.parseInt(firstLine[0]); int m = Integer.parseInt(firstLine[1]); 然后,构建邻接表: List<List<Integer>> adj = new ArrayList<>(); for (int i =0; i <=n; i++){ adj.add(new ArrayList<>()); } 然后读取m条边,每次读取两个整数u和v: for (int i=0; i<m; i++){ String[] line = br.readLine().split(" "); int u = Integer.parseInt(line[0]); int v = Integer.parseInt(line[1]); adj.get(u).add(v); adj.get(v).add(u); //因为是无向图 } 接下来,初始化dist和cnt数组: int[] dist = new int[n+1]; Arrays.fill(dist, -1); int[] cnt = new int[n+1]; int MOD = 100003; dist[1] =0; cnt[1] =1; 队列: Queue<Integer> queue = new LinkedList<>(); queue.add(1); 然后,BFS循环: while(!queue.isEmpty()){ int u = queue.poll(); for (int v : adj.get(u)) { if (dist[v] == -1) { dist[v] = dist[u] +1; cnt[v] = cnt[u] % MOD; queue.add(v); } else if (dist[v] == dist[u] +1) { cnt[v] = (cnt[v] + cnt[u]) % MOD; } } } 后,输出每个节点的cnt[i],从1到n: for (int i=1; i<=n; i++){ System.out.println(cnt[i] % MOD); } 这样是否正确? 测试样例: 比如样例输入: 4 5 1 2 1 3 2 4 3 4 3 4 邻接表中,节点1的邻接是2和3;节点2的邻接是1和4;节点3的邻接是1,4,4;节点4的邻接是2,3,3. 当处理节点1时,u=1,邻接是2和3。 处理v=2: dist[2]是-1,设置dist[2]=1,cnt[2]=1。入队。 处理v=3: dist[3]=-1,设置dist[3]=1,cnt[3]=1。入队。 队列现在有2和3. 处理u=2: 邻接是1和4. 处理1的时候,dist[1]=0,而dist[2]+1=1+1=2 !=0,不处理。 处理v=4: dist[4]是-1,设置dist[4]=2,cnt[4]=1. 入队. 队列现在有3和4. 处理u=3: 邻接是1,4,4. 处理1时,dist[1]=0,当前u=3的dist是1,所以dist[1] !=1+1=2,不处理。 处理第一个v=4: 此时,dist[4]是2,等于u的dist+11+1=2)。所以cnt[4] +=11+1=2. 然后处理第二个v=4(因为邻接表中有两个4),同样的情况,cnt[4] +=1 → 2+1=3. 队列中现在有4,然后处理u=4: 邻接是2、3、3. 处理v=2:dist[2]是1,当前u=4的dist是2,u.dist+1=3。而v的dist是1,不等于3,所以不处理。 同理,处理v=3时,dist[3]是1,u.dist+1=3,也不等于,所以不处理。所有处理完成后,队列空。 所以终cnt数组为: 1:1 2:1 3:1 4:3 符合样例输出。 这说明代码是正确的。 那么,Java代码的大体结构应该是这样的。需要注意的点是,当处理同一个节点的多个邻接边时,多次处理同一个v的情况是否正确累加。例如,节点3的邻接表中的两个4的情况,在两次处理时,都会进入else if的条件,导致两次累加,这样cnt[4]会变为3。 另外,在初始化的时候,MOD的值是否正确应用。例如,每次修改cnt时都取模,避免溢出。虽然Java的int是32位,但m可能高达2e5,每个cnt[u]可能被累加多次,所以每次累加后取模是必要的,防止溢出。 综上,这样的代码应该可以正确解决P1144问题。现在写出完整的Java代码。</think>要解决P1144短路计数问题,我们需要在无权图中找到从起点到各节点的最短路径数量。以下是详细的方法和实现步骤: ### 方法思路 1. **广度优先搜索 (BFS)**:由于图是无权的,BFS能逐层扩展,确保先到达的路径短的。 2. **距离数组 (dist)**:记录每个节点到起点的短距离。 3. **计数数组 (cnt)**:记录到达每个节点的最短路径数,遇到相同长度的路径时累加计数。 4. **处理重边和自环**:邻接表存储所有边,BFS会自动处理多次访问同一节点的情况。 ### 解决代码 ```java import java.io.*; import java.util.*; public class Main { public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String[] firstLine = br.readLine().split(" "); int n = Integer.parseInt(firstLine[0]); int m = Integer.parseInt(firstLine[1]); List<List<Integer>> adj = new ArrayList<>(); for (int i = 0; i <= n; i++) { adj.add(new ArrayList<>()); } for (int i = 0; i < m; i++) { String[] line = br.readLine().split(" "); int u = Integer.parseInt(line[0]); int v = Integer.parseInt(line[1]); adj.get(u).add(v); adj.get(v).add(u); } int[] dist = new int[n + 1]; Arrays.fill(dist, -1); int[] cnt = new int[n + 1]; final int MOD = 100003; dist[1] = 0; cnt[1] = 1; Queue<Integer> queue = new LinkedList<>(); queue.add(1); while (!queue.isEmpty()) { int u = queue.poll(); for (int v : adj.get(u)) { if (dist[v] == -1) { dist[v] = dist[u] + 1; cnt[v] = cnt[u] % MOD; queue.add(v); } else if (dist[v] == dist[u] + 1) { cnt[v] = (cnt[v] + cnt[u]) % MOD; } } } for (int i = 1; i <= n; i++) { System.out.println(cnt[i] % MOD); } } } ``` ### 代码解释 1. **输入处理**:使用`BufferedReader`高效读取输入数据,构建邻接表。 2. **初始化数组**:`dist`数组初始化为-1表示未访问,`cnt`数组记录路径数,起点初始化为1。 3. **BFS遍历**:队列处理每个节点,更新邻居节点的距离和路径数。遇到相同长度的路径时累加计数。 4. **结果输出**:遍历`cnt`数组,输出每个节点的最短路径数,结果对100003取模。 该方法确保在O(n + m)时间复杂度内高效解决问题,适用于大规模数据。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值