關於王道機試指南習題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;
}
思路二:(可以不用大數保存中間數據,每步運算都可以直接取模保存,因爲事先已經明確了大小關係,是代碼的內在邏輯)
- 題目給的數據有一個特點:越往後,給出的兩個點之間的距離就越大,且大於前面所有的邊的權值之和,那麼意味着,任意一個點在第一次和源點連通時,他們的距離即是最短距離。這個思路可以考慮一下。