acwing.1134最短路计数

https://www.acwing.com/problem/content/description/1136/

给出一个 N 个顶点 M 条边的无向无权图,顶点编号为 1 到 N。

问从顶点 1 开始,到其他每个点的最短路有几条。

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

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

输出格式
输出 N 行,每行一个非负整数,第 i 行输出从顶点 1 到顶点 i 有多少条不同的最短路,由于答案有可能会很大,你只需要输出对 100003 取模后的结果即可。

如果无法到达顶点 i 则输出 0。

数据范围
1≤N≤105,
1≤M≤2×105

思路:
首先,这类求最短路个数的问题不会出现权值和为0的环,这样某些cnt可以达到无穷。

其次,这道题表明没有负权边,边权值恒为1.因此也可以用bfs和双端队列

求某个点的cnt时我们要优先处理出其前面的所有点的cnt, 且对后面的cnt没有影响。所以cnt类似

于dp的求法,因此需要满足拓扑结构

我们处理出最短路树,满足拓扑图。

BFS 只入队一次,出队一次。可以抽象成拓扑图, 因为它可以保证被更新的点的父节点一定已经是

最短距离了,并且这个点的条数已经被完全更新过了。

dijkstra 每个点只出队一次。也可以抽象成拓扑图, 同理由于每一个出队的点一定已经是最短距
离,并且它出队的时候是队列中距离最小的点,这就代表他的最短距离条数已经被完全更新了,所以构成拓扑性质。

spfa 本身不具备拓扑序,因为每个点不止进出对一次,更新它的点不一定是最短距离

本身满足拓扑序的bfs和dijkstra都可以随着算法的操作逐步算出cnt,不满足拓扑序的spfa需要将其最短路树先处理出来,再对树dp算出cnt

bfs实现

#include <bits/stdc++.h>
#define MOD 100003
using namespace std;
const int N = 100010, M = 400010;
int n, m, tot;
int head[N], ver[M], ne[M], d[N], cnt[N];
int q[N];
void add(int x, int y) {
	ver[++tot] = y; ne[tot] = head[x], head[x] = tot;
}
void bfs() {
	memset(d, 0x3f, sizeof d);
	d[1] = 0, cnt[1] = 1;
	int hh = 0, tt = 0;
	q[0] = 1;
	while (hh <= tt) {
		int x = q[hh++];
		for(int i = head[x]; i; i = ne[i]) {
			int y = ver[i];
			if (d[y] > d[x] + 1) {
				d[y] = d[x] + 1;
				cnt[y] = cnt[x];
				q[++tt] = y;
			}
			else if (d[y] == d[x] + 1) cnt[y] = (cnt[x] + cnt[y]) % MOD;
		}
	}
}
int main () {
	cin >> n >> m;
	memset(head, -1, sizeof head);
	while(m--) {
		int a, b;
		cin >> a >> b;
		add(a ,b); add(b, a);
	}
	bfs();
	for (int i = 1; i <= n; i++) cout << cnt[i] << endl;
	return 0;
}

dijkstra实现

#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
const int N = 100010, M = 400010, MOD = 100003;
int head[N], ver[M], ne[M], d[N], cnt[N];
bool v[N];
int n, m, tot;
priority_queue<pair <int, int> >q;
void add (int x, int y) {
	ver[++tot] = y, ne[tot] = head[x], head[x] = tot;
} 
void dijkstra () {
	memset(d, 0x3f, sizeof d);
	memset(v, 0, sizeof v);
	d[1] = 0; cnt[1] = 1;
	q.push(make_pair(0, 1));
	while (q.size()) {
		int x = q.top().second; q.pop();
		if (v[x]) continue;
		v[x] = 1;
		for (int i = head[x]; i; i = ne[i]) {
			int y = ver[i];
			if (d[y] > d[x] + 1) {
				cnt[y] = cnt[x];
				d[y] = d[x] + 1;
				q.push(make_pair(-d[y], y));
			}
			else if (d[y] == d[x] + 1) cnt[y] = (cnt[y] + cnt[x]) % MOD;
		}
	}	
}
int main () {
	cin >> n >> m;
	memset(head, -1, sizeof head);
	while (m--) {
		int a, b;
		cin >> a >> b;
		add(a, b); add(b, a);
	}
	dijkstra();
	for (int i = 1; i <= n; i++) cout << cnt[i] << endl;
	return 0;
}

虽然spfa需要预处理出最短路树,但是当出现负权边等情况下,我们还是需要使用spfa
spfa

#include <bits/stdc++.h>
#define MOD 100003
using namespace std;
const int N = 100010, M = 2 * 400010;
int n, m, tot;
int head[N], h[N], ver[M], ne[M], d[N], cnt[N], in[N], q[N];
bool v[N];
void add(int *h, int x, int y) {
	ver[++tot] = y; ne[tot] = h[x], h[x] = tot;
}
void spfa() {
	memset(d, 0x3f, sizeof d);
	memset(v ,0, sizeof v);
	queue<int> q;
	d[1] = 0, v[1] = 1;
	q.push(1);
	while (q.size()) {
		int x = q.front(); q.pop();
		v[x] = 0;
		for (int i = head[x]; i; i = ne[i]) {
			int y = ver[i];
			if (d[y] > d[x] + 1) {
				d[y] = d[x] + 1;
				if (!v[y]) q.push(y), v[y] = 1;
			}
		}
	}
}
void build () {
	memset(h, -1, sizeof h);
	for (int x = 1; x <= n; x++) {
		for(int i = head[x]; i; i = ne[i]) {
			int y = ver[i];
			if (d[y] == d[x] + 1) {
				in[y]++;
				add(h, x, y);
			} 
		}
	}
}
void topsort() {
	int hh = 0, tt = -1;
	q[++tt] = 1;
	cnt[1] = 1;
	while(hh <= tt) {
		int x = q[hh++];
		for (int i = h[x]; i; i = ne[i]) {
			int y = ver[i];
			cnt[y] = (cnt[x] + cnt[y]) % MOD;
			in[y]--;
			if (in[y] == 0) q[++tt] = y;
		}
	}
}
int main () {
	cin >> n >> m;
	memset(head, -1, sizeof head);
	while(m--) {
		int a, b;
		cin >> a >> b;
		add(head, a ,b); add(head, b, a);
	}
	spfa();
	build();
	topsort();
	for (int i = 1; i <= n; i++) cout << cnt[i] << endl;
	return 0;
}
posted @ 2022-04-20 17:07  misasteria  阅读(27)  评论(0编辑  收藏  举报