acwing.1134最短路计数
给出一个 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;
}