试题D:路径
题目描述
小蓝学习了最短路径之后特别高兴,他定义了一个特别的图,希望找到图中的最短路径。小蓝的图由2021 个结点组成,依次编号1 至2021。
对于两个不同的结点a, b,如果a 和b 的差的绝对值大于21,则两个结点之间没有边相连;如果a 和b 的差的绝对值小于等于21,则两个点之间有一条长度为a 和b 的最小公倍数的无向边相连。
例如:结点1 和结点23 之间没有边相连;结点3 和结点24 之间有一条无向边,长度为24;
结点15 和结点25 之间有一条无向边,长度为75。
请计算,结点1 和结点2021 之间的最短路径长度是多少。
提示:建议使用计算机编程解决问题。
解析:
一、前置知识
问题1:怎么求两个数a,b的最小公倍数呢?
答:【公式】 a , b 最小公倍数 = a × b / a , b 的最大公因数 a,b最小公倍数 = a \times b / a,b的最大公因数 a,b最小公倍数=a×b/a,b的最大公因数.
问题2:怎么求最大公因数呢?
答:辗转相除法
public static int gcd(int a,int b){
return b==0?a:gcd(b,a%b);
}
问题3:阈值的选取
答:这一步很重要,下午调试了很久都是因为阈值没选好。阈值不能很大,因为这样可能会使扩展部分的加法查出long型范围;阈值不能太小,太小导致答案在阈值外面。
问题4:long型和Long型的区别
int转long型自然转换,int转Long型强制转换。
二、分析
1、数据分析
- 0 < i 和 j 两个节点之间的差的绝对值 <22,distance[i][j] =a,b最小公倍数
- i 和 j 两个节点之间的差的绝对值==0,distance[i][j] =0
- i 和 j 两个节点之间的差的绝对值 > 21,distance[i][j] = 阈值,阈值十分重要,本次屡次在这犯错。
2、过程分析
在图中求一个点到另一个点的距离,可以使用Dijkstra和Floyd方法。可以使用Floyd方法是因为本题是填空题,时间复杂度不重要。
关于Dijkstra和Floyd方法可以查看Dijkstra和Floyd
三、代码
1、Dijkstra代码
public class ExaminationD_Dijkstra {
//求解最大公因数
public static long gcd(long a,long b){
return b==0?a:gcd(b,a%b);
}
//求解a,b的最小公倍数
public static long lcm(long a,long b){
return a*b/gcd(a,b);
}
public static long[] dijkstra(long[][] distance,int n,int origin){
//辅助数组
boolean[] used = new boolean[n];
long[] minDis = new long[n];
int pathIndex = origin-1;
//初始化数组
for (int i = 0; i < n; i++) {
minDis[i] = distance[origin-1][i];
}
used[origin-1] = true;
for (int i = 1; i < n; i++) {
long min = Long.MAX_VALUE;
//选取部分
for (int j = 0; j < n; j++) {
if (!used[j]&&minDis[j]<min) {
//System.out.println(String.format("变化前minDis[%d]=%d,min=%d",j,minDis[j],min));
min = minDis[j];
//System.out.println(String.format("变化后minDis[%d]=%d,min=%d",j,minDis[j],min));
pathIndex = j;
}
}
used[pathIndex]= true;
//扩展部分
for (int k = 0; k < n; k++) {
if (distance[pathIndex][k]!=Long.MAX_VALUE) {//选取合适的阈值使答案在这个范围又要使下面的加法不差过long范围
if (minDis[pathIndex]+distance[pathIndex][k]<minDis[k]) {
minDis[k] = minDis[pathIndex] + distance[pathIndex][k];
System.out.println(String.format("扩展minDisk[%d]=%d",k,minDis[k]));
}
}
}
}
return minDis;
}
public static void main(String[] args) {
int n = 2021;
long[][] dis = new long[n][n];
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (Math.abs(i-j)<=21&&Math.abs(i-j)>0) {
dis[i][j] = dis[j][i] = lcm(i+1,j+1);
}else if(i==j){
dis[i][j] = 0;//在扩展部分可能会加上去
}else if(Math.abs(i-j)>21){
dis[i][j] = Long.MAX_VALUE;
}
}
}
long[] res = dijkstra(dis, n, 1);
System.out.println(res[n-1]);
}
}
2、Floyd代码
//麻烦的是怎么设定一个阈值
public class ExaminationD_Floyd {
//求解最大公因数
public static long gcd(long a,long b){
return b==0?a:gcd(b,a%b);
}
//求解a,b的最小公倍数
public static long lcm(long a,long b){
return a*b/gcd(a,b);
}
public static long[][] floyd(long[][] distance,int n,int origin){
for (int k = 0; k <n; k++) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (distance[k][j]!=99999999L) {
//设定阈值使小于阈值的两个数相加不超过long范围
if (distance[i][k]+distance[k][j]<distance[i][j]) {
distance[i][j] = distance[i][k]+distance[k][j];
}
}
}
}
}
return distance;
}
public static void main(String[] args) {
int n = 2021;
long[][] dis = new long[n][n];
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (Math.abs(i-j)<=21&&Math.abs(i-j)>0) {
dis[i][j] = dis[j][i] = lcm(i+1,j+1);
}else if(i==j){
dis[i][j] = 0;
}else if(Math.abs(i-j)>21){
dis[i][j] = 99999999L;
}
}
}
long res[][] = floyd(dis,n,1);
System.out.println(res[0][n-1]);
}
}