题目描述:N个城市,标号从0到N-1,M条道路,第K条道路(K从0开始)的长度为2^K,求编号为0的城市到其他城市的最短距离
解题思路(方法一):Dijkstra算法 + string形式存储路径长度
这个题目我花了很久的时间才通过牛客网的在线测试,在此记录我踩一下过的坑
- 首先题目说的是2的K次幂,我一开始给理解成了2的K倍
- 刚开始我是用int类型保存路径长度的,题目说“数值太大的以MOD 100000 的结果输出”,我就在求解过程中不断求余,把求余的结果保存为路径长度。后来测试一直没通过,我隔了一天才反应过来,保存余数是错误的,例如:本来路径长度是100008和9999,求余后,变成8和9999,8 < 9999跟100008 > 9999是完全对应不上的。意识到这个错误后,我就开始用string类型保存路径长度,增加了4个有关操作string的函数。
- 经过以上的纠错,我提交代码,发现还是没通过测试,我对比了自己代码的输出和标准输出,发现让人迷惑的是,有些行的结果是正确的,有些却不是。我第一反应是去检查我手写的那4个有关操作string的函数,检查来检查去,发现没有什么很大的bug,于是我一时间无从下手。又过了一天,我尝试对结构体Point的小于号重载函数进行修改,发现程序的结果有了很大改变,虽然仍然没有通过,但是我隐约意识到,程序的bug与这个地方有关。后来果真,是jud函数里compare方法用得不对,直接用<、>、=便能达到程序需要的判断效果。
代码:
#include <iostream>
#include <vector>
#include <queue>
#include <cstring>
#include <climits>
using namespace std;
const int MAXN = 101;
const int MOD = 100000;
// 比较两个数字字符串的大小,s1<s2: -1,s1==s2: 0,s1>s2: 1
int jud(string s1,string s2) {
int len1 = s1.size();
int len2 = s2.size();
if(len1==len2)
if(s1<s2) return -1;
else if(s1==s2) return 0;
else return 1;
else if(len1<len2)
return -1;
else
return 1;
}
/*
int jud(string s1,string s2) {
int len1 = s1.size();
int len2 = s2.size();
if(len1==len2)
return s1.compare(s2);
else if(len1<len2)
return -1;
else
return 1;
}*/
// 两个数字字符串 求和操作
string Add(string s1,string s2) {
if(s1.size() < s2.size())
swap(s1,s2); // 预设定:s1长度较长
string res = "";
int len1 = s1.size(),len2 = s2.size(),carry = 0;
for(int i=len1-1,j=len2-1; i>=0; i--,j--) {
if(j>=0)
carry += (s1[i]-'0') + (s2[j]-'0');
else
carry += (s1[i]-'0');
char temp = (carry%10 + '0');
res = temp + res;
carry /= 10;
}
while(carry>0) {
char temp = (carry%10 + '0');
res = temp + res;
carry /= 10;
}
return res;
}
// 数字字符串 乘法操作(乘以一个 个位数字num)
string Multiple(string s1,int num) {
string res = "";
int len1 = s1.size(),carry = 0;
for(int i=len1-1; i>=0; i--) {
carry += (s1[i]-'0')*num;
char temp = (carry%10 + '0');
res = temp + res;
carry /= 10;
}
while(carry>0) {
char temp = (carry%10 + '0');
res = temp + res;
carry /= 10;
}
int pos;
for(pos=0;pos<res.size()-1;pos++){
if(res[pos]!='0') return res.substr(pos);
}
return res.substr(pos);
}
// 将string类型转换为int类型,数值太大就 MOD 100000
int Tran(string s1){
int remainder = 0;
int len = s1.size();
for(int i=0;i<len;i++){
remainder*=10;
int temp = s1[i] - '0';
remainder += temp;
remainder %= MOD;
}
if(remainder==0){
if(len==0|| len==1) return 0;
else return MOD;
}else return remainder;
}
struct Edge {
int to;
string length;
Edge(int t,string l):to(t),length(l) {}
};
struct Point {
int number;
string distance;
Point(int n,string d):number(n),distance(d) {}
bool operator< (const Point& y) const {
if(jud(distance,y.distance)==-1)
return false;
return true;
}
};
vector<Edge> graph[MAXN];
string dis[MAXN];
void Dijkstra(int s){
dis[s] = "0";
priority_queue<Point> myQueue;
myQueue.push(Point(s,dis[s]));
while(myQueue.empty()==false){
int u =myQueue.top().number;
myQueue.pop();
for(int i=0;i<graph[u].size();i++){
int p = graph[u][i].to;
string dd = graph[u][i].length;
if(dis[p]=="INF" || jud(dis[p],Add(dis[u],dd)) == 1){
dis[p] = Add(dis[u],dd);
myQueue.push(Point(p,dis[p]));
}
}
}
}
int main() {
int N,M;
while(cin>>N>>M){
memset(graph,0,sizeof(graph));
fill(dis,dis+N,"INF");
string K = "1";
int p1,p2;
for(int i=1;i<=M;i++){
cin>>p1>>p2;
graph[p1].push_back(Edge(p2,K));
graph[p2].push_back(Edge(p1,K));
K = Multiple(K,2);
}
Dijkstra(0);
for(int i=1;i<N;i++){
if(dis[i]=="INF") cout<<"-1"<<endl;
else cout<<Tran(dis[i])<<endl;
}
}
return 0;
}
解题思路(方法二):这是一道披着最短路径外衣的最小生成树。
第k条路径的长度 > 前k-1条路径的总和
- 在添加第k条路径时,如果路径k连接的两个城市A 、B已经通过其他路径间接的连通了,那么第k条路径绝对不是AB间的最短路径(第k条路径之后的路径也不可能有比当前AB路径更短的路径了,因为第k条路径的长度会大于前k-1条路径的总和)
- 在添加第k条路径时,如果路径k连接的两个城市A 、B是第一次被连通了(也就是说此前没有任何路径能连通AB,包括通过多条路径间接连通),那么路径k就是AB之间的最短距离了,因为以后不可能存在更短的路径连接AB,以后的单条路径只会越来越长。
- 构造一个并查集,每个城市指向自己,二维数组dis记录城市之间的距离,初始化为INF不可达,dis[i][i]初始化为0
- 取第一条路径,第一条路径连接了城市p1-1、p2-1,城市p1-1、p2-1间最短路径就是第一条路径,然后把p1-1、p2-1合并在一个集合里
- 再取一条路径
- 如果这条边连接的城市p1-2、p2-2,已经在同一个集合里了(即已经联通了),那么当前的路径一定不是p1-2、p2-2之间的最短路径了,忽略之,继续看下一条路径。
- 如果这条路径连接的城市p1-2、p2-2不在同一个集合里(之前没有联通过),很好,这条路径是p1-2、p2-2之间的最短路径;除此之外我们还可以看看通过这条路径能不能让p1-2集合里的城市到p2-2集合里的城市之间的路径更短呢?即是不是dis[s][t]>(d[s][p1-2]+dist+d[p2-2][t]) ?(注意s和p1-2一个集合,p1-2和t一个集合),如果是的话可以更新一下这个值。
- 重复(3)直到所有路径都处理过了
- 0号城市到各个城市i的最小路径结果就是dis[0][i]了
#include <iostream>
#include <vector>
#include <queue>
#include <cstring>
#include <climits>
using namespace std;
const int MAXN = 101;
const int MOD = 100000;
const int INF = INT_MAX;
int dis[MAXN][MAXN]; // 无向图
int father[MAXN];
int height[MAXN];
void Initial(int n) {
memset(dis,INF,sizeof(dis));
for(int i=0; i<n; i++) {
father[i] = i;
height[i] = 0;
for(int j=0; j<n; j++) {
if(i==j)
dis[i][j]=0;
else
dis[i][j]=INF;
}
}
}
int Find(int x) {
if(father[x]!=x)
father[x] = Find(father[x]);
return father[x];
}
void Union(int x,int y) {
int root1 = Find(x);
int root2 = Find(y);
if(root1!=root2) {
if(height[root1]<height[root2])
father[root1] = root2;
else if(height[root1]>height[root2])
father[root2] = root1;
else {
father[root2] = root1;
height[root1]++;
}
}
}
int main() {
int N,M;
while(cin>>N>>M) {
Initial(N);
int K = 1;
int p1,p2;
for(int i=1; i<=M; i++) {
cin>>p1>>p2;
/*if(Find(p1)!=Find(p2)) {
Union(p1,p2);
dis[p1][p2] = K;
dis[p2][p1] = K;
for(int s=0; s<N; s++) {
for(int t=0; t<N; t++) {
if(dis[s][t]==INF && dis[s][p1]!=INF && dis[p2][t]!=INF) {
dis[s][t] = (dis[s][p1] + K + dis[p2][t])%MOD;
}
}
}
}*/
if(Find(p1)!=Find(p2)){ /// 这个if下的代码段,比较关键,需要通过Find方法来判断dis的元素是否需要更新而不是通过INF来判断
for(int s=0;s<N;s++){
if(Find(s)==Find(p1)){
for(int t=0;t<N;t++){
if(Find(t)==Find(p2)){
dis[s][t]=dis[t][s]=(dis[s][p1]+dis[t][p2]+K)%MOD;
}
}
}
}
Union(p1,p2); // 这个要放在最后,不然前面循环中的判断将永远不会执行
}
K = (K*2)%MOD;
}
for(int i=1; i<N; i++) { // 输出结果
if(dis[0][i]==INF)
cout<<"-1"<<endl;
else
cout<<dis[0][i]<<endl;
}
}
return 0;
}