最近又在牛客网刷到这个题目了,题目链接
然而,很悲催地,还是按着常规的思路采用最短路径写,依旧WA。一方面,之前刷过的题目,还是得经常温故,另一方面,训练的题目还是太少,继续加油~
Description
N个城市,标号从0到N-1,M条道路,第K条道路(K从0开始)的长度为2^K,求编号为0的城市到其他城市的最短距离。
Input
第一行两个正整数N(2<=N<=100)M(M<=500),表示有N个城市,M条道路,
接下来M行两个整数,表示相连的两个城市的编号。
Output
N-1行,表示0号城市到其他城市的最短路,如果无法到达,输出-1,数值太大的以MOD 100000 的结果输出。
参考链接:1956 Problem C 最短路径
最终AC代码如下:
#include <bits/stdc++.h> using namespace std; const int MAX=105, MOD=1e5, INF=-1; //这里注意INF是-1而不是无穷大(虽然本题写成无穷大也能通过) int father[MAX], d[MAX][MAX]; int findFather(int x){ //找父节点 if(x != father[x]) father[x] = findFather(father[x]); //递归寻找父节点并压缩路径 return father[x]; } int quickMi(int a, int k){ int ans = 1; long long int res = a; //这里res用int容易溢出 while(k){ if(k & 1) ans = (ans * res) % MOD; res = (res * res) % MOD; //这里不要写成 res = (res * a) % MOD; k >>= 1; } return ans; } int main(){ int i, j, k, u, v, fu, fv, n, m, dis; while(scanf("%d %d", &n, &m) != EOF){ fill(d[0], d[0]+MAX*MAX, INF); //初始化距离数组 for(i=0; i<n; i++){ father[i] = i; //初始化并查集 d[i][i] = 0; //初始化距离数组对角线 这里忘记 答案一定错 } for(k=0; k<m; k++){ scanf("%d %d", &u, &v); fu = findFather(u); //找u的父节点fu fv = findFather(v); if(fu != fv){ //说明u,v未连通 dis = quickMi(2, k); //由于指数太大 采用快速幂 for(i=0; i<n; i++){ if(fu == findFather(i)){ //找到以 x 为根节点的集合上所有点 for(j=0; j<n; j++){ if(fv == findFather(j)){ //找到以 y 为根节点的集合上所有点 //注意以下更新d[i][j]和d[j][i]一起更新 d[i][j] = d[j][i] = (d[i][u] + dis + d[v][j]) % MOD; //更新 } } } } father[fu] = fv; //合并这两个集合 } } for(j=1; j<n; j++) printf("%d ", d[0][j]); } return 0; }
总结:这题的关键麻烦就是指数可能会很大,于是不能用一半的求最短路的方式求解。然后,由求快速幂取模的方式,可以解决指数幂次很大的情况。于是存储取余后的路径后,再尝试用Dijkstra等求最短路径,结果依然是WA,原因是路径取模不精确。
后来,参考别人的博客时,发现别人采用快速幂+并查集的方式很简便的解决了,主要思想是:如果两个点已经在一个集合中了,由于后面输入的边是递增的,所以不用再更新;如果两个点在两个不同集合上,那么说明这两个集合没有边连接,那么更新路径数组,再合并集合。