题目描述
N个城市,标号从0到N-1,M条道路,第K条道路(K从0开始)的长度为2^K,求编号为0的城市到其他城市的最短距离
输入描述:
第一行两个正整数N(2<=N<=100)M(M<=500),表示有N个城市,M条道路
接下来M行两个整数,表示相连的两个城市的编号
输出描述:
N-1行,表示0号城市到其他城市的最短路,如果无法到达,输出-1,数值太大的以MOD 100000 的结果输出。
输入
4 4
1 2
2 3
1 3
0 1
输出
8
9
11
思路小结
(1)本来以为是Dijkstra最短路径算法,但一看这权值,根本存不下,只能上网查阅,发现有个大数模处理方法。取模运算具有一系列良好的性质,这些性质总结下来基本就是许多数字的加减乘除的结果对某一数求余等于每个数字求余之后再进行加减乘除操作。
即 (a+b)%m = (a%m +b%m)%m
(a*b)%m = (a%m *b%m)%m
(2)但是这些路径长度先取余运算后,大小比较就不行了,所以Dijkstra算法基本使用不了,只好再次查阅大神的做法,发现这个输入路径长度呈递增排列,刚好符合最小生成树Kruskal算法的前序工作,这竟然是一道披着最短路径外衣的最小生成树算法。
(3)由于对于最小生成树掌握不周,本来最小生成树是用来寻找连通图中的最小权连通子图的,现在将它的思想应用于此,对于为何这样做求出来的就是对应的最短路径,还是有点不解。
(4)具体做法是使用一个不相交集,一开始所有结点都各成一树,迭代加入权重最小的边,本题输入就是排好序的,然后将此边对应的2点进行合并操作,但此题最关键的是任意2点之间最短路径长度的记录,这也有点难以理解。每次需要合并2棵树的时候,就需要对记录的长度值进行更新,更新公式为: dis[j][k] = dis[k][j] = (dis[j][x] + dist + dis[k][y]) % 100000;
这个公式可以这样理解,2棵树中对应的结点分别为j和k,那么合并之后j,k之间最短距离就是由j到根节点x的最短距离+根节点x到根节点y的最短距离dist+k到根节点y的最短距离之和。
#include<iostream>
#include<stdlib.h>
#include<stdio.h>
using namespace std;
const int maxn = 101;
const int maxm = 502;
int dis[maxn][maxn];
int T[maxn];
int find(int x) {
if (T[x] == x) return x;
else {
return find(T[x]);
}
}
void unio(int x, int y) {
int xparent = find(x);
int yparent = find(y);
xparent > yparent ? T[xparent] = yparent : T[yparent] = xparent;
}
int main() {
int N, M;
cin >> N >> M;
int dist = 1;
for (int i = 0; i < N; i++) {
T[i] = i;
dis[i][i] = 0;
}
for (int i = 0; i < M; i++) {
int x, y;
cin >> x >> y;
if (find(x) != find(y)) {
for (int j = 0; j < N; j++) {
if (find(x) == find(j)) {
for (int k = 0; k < N; k++) {
if (find(y) == find(k)) {
dis[j][k] = dis[k][j] = (dis[j][x] + dist + dis[k][y]) % 100000;
}
}
}
}
}
unio(x, y);
dist = dist * 2 % 100000;
}
for (int i = 1; i < N; i++) {
if (find(0) != find(i)) cout << -1 << endl;
else {
cout << dis[0][i] << endl;
}
}
return 0;
}