关于王道机试指南习题11.6 上海交通大学复试上机题解:最短路径
之前存在的问题:当专门为大数定义一个结构体时,那么在新的结构体内无法利用大数的结构体内已经重定义的运算符;
两个思路:
思路一:
- 以字符串存储大数,如此在Edge和Point的结构体内就可以利用字符串的比较规则;
- 由于字符串比较是按字典序比较,为了保证两个大数能够直接比较,必须要让所有大数的位数相同,如此才能直接比较。否则会出现明明大数x更大,位数更多,但反而因为大数x的高位比大数y的高位小而得出x<y的结论。
- 由题意可知,题中两地的距离最大不过2^500次方,且距离都是2的n次方,因此我们可以以二进制来对距离进行统计。由此我们对每个大数都定义一个位数为500大小的字符串,str[0]属于高位,str[500-1]属于低位,不足高位我们补0,以方便后续的大数作比较。
- 另外,对于大数的字符串的位数,我们应当在500基础上加一个额外的安全量,定义为MAXM=512;
- 利用Dijkstra算法得出各点到源点的最短距离;
- 最后输出的时候利用快速幂的方法得到距离对mod=100000的余数;
- 快速幂有:和的模=模的和,积的模=模的积 的特点;
- 这个思路用大数的必要性:Dijkstra算法在过程中会进行dist[i]的大小比较,如果不使用大数,那么就要在过程中利用快速幂的方法不断取模,而在过程中取模后得到的大小比较结果是有误的,因此必须要用大数来保存这个中间数据;
代码如下:
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <queue>
using namespace std;
const int MAXN = 110;
const int MAXM = 512;
typedef string BigInteger;
struct Edge {
int to;
BigInteger distance;
Edge(int t, BigInteger d) :to(t), distance(d) {}
};
struct Point {
int number;
BigInteger distance;
Point(int n, BigInteger d) :number(n), distance(d) {}
bool operator<(const Point& p) const {
return distance > p.distance;
}
};
vector<Edge> graph[MAXN];
BigInteger dist[MAXN];
BigInteger getBigInt(int i) {//大数(二进制表示)从低到高的第(i+1)位赋值为1
string str;
str.insert(0, MAXM, '0');
str[MAXM - i - 1] = '1';
return str;
}
BigInteger addString(BigInteger a, BigInteger b) {
string ans;
for (int i = 0; i < MAXM; i++) {
ans.append(1, a[i] - '0' + b[i] - '0' + '0');
}
return ans;
}
const string INF = getBigInt(511);
void Dijkstra(int s) {
priority_queue<Point> myPriorityQueue;
dist[s][0] = '0';//点s到源点s的最近距离是0
myPriorityQueue.push(Point(s, dist[s]));
while (!myPriorityQueue.empty()) {
int u = myPriorityQueue.top().number;
myPriorityQueue.pop();
for (int i = 0; i < graph[u].size(); i++) {
int v = graph[u][i].to;
BigInteger d = graph[u][i].distance;
if (dist[v] > addString(dist[u], d)) {
dist[v] = addString(dist[u], d);
myPriorityQueue.push(Point(v, dist[v]));
}
}
}
return;
}
int StrToInt(string str) {//利用快速幂得到对应的十进制数对100000的取余
int mod = 100000;
int answer = 0, multiple = 1;
for (int i = MAXM-1; i>=0; i--) {
if (str[i] == '1') {
answer += multiple;
answer %= mod;
}
multiple *= 2;
multiple %= mod;
}
return answer;
}
int main() {
int n, m;
while (scanf("%d%d", &n, &m) != EOF) {
memset(graph, 0, sizeof(graph));
for (int i = 0; i < n; i++) {
dist[i] = INF;
}
int from, to;
BigInteger distance;
for (int i = 0; i < m; i++) {
scanf("%d%d", &from, &to);
distance = getBigInt(i);
graph[from].push_back(Edge(to, distance));
graph[to].push_back(Edge(from, distance));
}
Dijkstra(0);
for (int i = 1; i < n; i++) {
if (dist[i] == INF) {
printf("-1\n");
continue;
}
printf("%d\n", StrToInt(dist[i]));
}
}
return 0;
}
思路二:(可以不用大数保存中间数据,每步运算都可以直接取模保存,因为事先已经明确了大小关系,是代码的内在逻辑)
- 题目给的数据有一个特点:越往后,给出的两个点之间的距离就越大,且大于前面所有的边的权值之和,那么意味着,任意一个点在第一次和源点连通时,他们的距离即是最短距离。这个思路可以考虑一下。