题目:
试题名称:行车路线
时间限制:1.0s
内存限制:256.0MB
问题描述
小明和小芳出去乡村玩,小明负责开车,小芳来导航。
小芳将可能的道路分为大道和小道。大道比较好走,每走1公里小明会增加1的疲劳度。小道不好走,如果连续走小道,小明的疲劳值会快速增加,连续走s公里小明会增加s2的疲劳度。
例如:有5个路口,1号路口到2号路口为小道,2号路口到3号路口为小道,3号路口到4号路口为大道,4号路口到5号路口为小道,相邻路口之间的距离都是2公里。如果小明从1号路口到5号路口,则总疲劳值为(2+2)2+2+22=16+2+4=22。
现在小芳拿到了地图,请帮助她规划一个开车的路线,使得按这个路线开车小明的疲劳度最小。
小芳将可能的道路分为大道和小道。大道比较好走,每走1公里小明会增加1的疲劳度。小道不好走,如果连续走小道,小明的疲劳值会快速增加,连续走s公里小明会增加s2的疲劳度。
例如:有5个路口,1号路口到2号路口为小道,2号路口到3号路口为小道,3号路口到4号路口为大道,4号路口到5号路口为小道,相邻路口之间的距离都是2公里。如果小明从1号路口到5号路口,则总疲劳值为(2+2)2+2+22=16+2+4=22。
现在小芳拿到了地图,请帮助她规划一个开车的路线,使得按这个路线开车小明的疲劳度最小。
输入格式
输入的第一行包含两个整数n, m,分别表示路口的数量和道路的数量。路口由1至n编号,小明需要开车从1号路口到n号路口。
接下来m行描述道路,每行包含四个整数t, a, b, c,表示一条类型为t,连接a与b两个路口,长度为c公里的双向道路。其中t为0表示大道,t为1表示小道。保证1号路口和n号路口是连通的。
接下来m行描述道路,每行包含四个整数t, a, b, c,表示一条类型为t,连接a与b两个路口,长度为c公里的双向道路。其中t为0表示大道,t为1表示小道。保证1号路口和n号路口是连通的。
输出格式
输出一个整数,表示最优路线下小明的疲劳度。
样例输入
6 7
1 1 2 3
1 2 3 2
0 1 3 30
0 3 4 20
0 4 5 30
1 3 5 6
1 5 6 1
1 1 2 3
1 2 3 2
0 1 3 30
0 3 4 20
0 4 5 30
1 3 5 6
1 5 6 1
样例输出
76
样例说明
从1走小道到2,再走小道到3,疲劳度为52=25;然后从3走大道经过4到达5,疲劳度为20+30=50;最后从5走小道到6,疲劳度为1。总共为76。
数据规模和约定
对于30%的评测用例,1 ≤ n ≤ 8,1 ≤ m ≤ 10;
对于另外20%的评测用例,不存在小道;
对于另外20%的评测用例,所有的小道不相交;
对于所有评测用例,1 ≤ n ≤ 500,1 ≤ m ≤ 105,1 ≤ a, b ≤ n,t是0或1,c ≤ 105。保证答案不超过106。
对于另外20%的评测用例,不存在小道;
对于另外20%的评测用例,所有的小道不相交;
对于所有评测用例,1 ≤ n ≤ 500,1 ≤ m ≤ 105,1 ≤ a, b ≤ n,t是0或1,c ≤ 105。保证答案不超过106。
解法1:暴力DFS
思路:搜索所有可能的路径,更新最小值。
结果:超时(30分)
代码:
import java.util.Scanner; public class Main{ final int Max_N = 500; int n; long shortest = -1; Edge[][] edge = new Edge[Max_N][Max_N]; boolean[] isInP = new boolean[Max_N]; //记录路径 void run(){ Scanner in = new Scanner(System.in); n = in.nextInt(); int m = in.nextInt(); in.nextLine(); for(int i = 0;i < m; i++){ int t = in.nextInt(); int a = in.nextInt(); int b = in.nextInt(); int c = in.nextInt(); in.nextLine(); edge[a-1][b-1] = edge[b-1][a-1] = new Edge(t,c); } in.close(); DFS(0,new long[2]); System.out.println(shortest); } void DFS(int index,long[] dis){ if(n - 1 == index){ if(shortest == -1 || dis[0] < shortest) shortest = dis[0]; return ; } long[] lastdis = new long[2]; lastdis[0] = dis[0];lastdis[1] = dis[1]; for(int i = 0; i < n; i++){ if(!isInP[i] && edge[index][i] != null){ long w = edge[index][i].weight; if(edge[index][i].type == 1){ //(x1 + x2)^2 = x1^2 + 2*x1*x2 + x2^2 //(x1 + x2 + x3)^2 = (x1 + x2)^2 + 2*(x1 + x2)*x3 +x3^2 dis[0] += (w*w + 2*dis[1]*w); dis[1] += w; }else{ dis[0] += w; dis[1] = 0; } if(shortest ==-1 || dis[0] < shortest) { //小优化 isInP[i] = true; DFS(i,dis); isInP[i] = false; } dis[0] = lastdis[0]; dis[1] = lastdis[1]; } } return ; } public static void main(String[] args) { new Main().run(); } } class Edge{ public int type; public long weight; public Edge(int t, long w){ this.type = t; this.weight = w; } }
解法2:
思路:使用dijkstra算法求解最短路径,每次走小路时记录小路的总长。
结果:错误(90分),错误的原因是小路的存在导致得到的解只是次优。
代码:
import java.util.Scanner; public class Main{ final int Max_N = 500; final long INF = (long) 1e9; long shortest = -1; int n; Edge[][] edge = new Edge[Max_N][Max_N]; void run(){ Scanner in = new Scanner(System.in); n = in.nextInt(); int m = in.nextInt(); in.nextLine(); for(int i = 0;i < m; i++){ int t = in.nextInt(); int a = in.nextInt(); int b = in.nextInt(); int c = in.nextInt(); in.nextLine(); edge[a-1][b-1] = edge[b-1][a-1] = new Edge(t,c); } in.close(); dijkstra(0); System.out.println(shortest); } void dijkstra(int start){ long[][] dis = new long[n][2]; boolean[] isVisit = new boolean[n]; for(int i = 0;i < n; i++){ if(edge[start][i] != null){//有路 long w = edge[start][i].weight; if(edge[start][i].type == 1){//小路。 dis[i][0] = w * w; //疲劳值。 dis[i][1] = w; //小路的总长度。 }else { dis[i][0] = w; dis[i][1] = 0; } }else{//无路 dis[i][0] = INF; dis[i][1] = 0; } } dis[start][0] = 0; dis[start][1] = 0; isVisit[start] = true; for(int i = 0; i < n; i++){ int u = -1;long min = INF; for(int j = 0; j < n; j++){ if(!isVisit[j] && dis[j][0] < min){ min = dis[j][0]; u = j; } } if(u == -1 || u == n - 1){ shortest = dis[ n - 1][0]; return ; } isVisit[u] = true; for(int j = 0; j < n; j++){ if(!isVisit[j] && edge[u][j] != null ){ //优化未走过的点 long w = edge[u][j].weight; if(edge[u][j].type == 0){ //走大路过去 if(w + dis[u][0] < dis[j][0]){ dis[j][0] = dis[u][0] + w; dis[j][1] = 0; } }else {//走小路过去 if(w*w + 2*w*dis[u][1] + dis[u][0] < dis[j][0]){ //(x1 + x2)^2 = x1^2 + 2*x1*x2 + x2^2 //(x1 + x2 + x3)^2 = (x1 + x2)^2 + 2*(x1 + x2)*x3 +x3^2 dis[j][0] = w*w + 2*w*dis[u][1] + dis[u][0]; dis[j][1] = dis[u][0] + w; } } } } } } public static void main(String[] args) { new Main().run(); } } class Edge{ public int type; public long weight; public Edge(int t, long w){ this.type = t; this.weight = w; } }
解法3 :
思路:把小路与大路分开,用两个图表示。分别用最短路径算法求解。
结果:错误(60分)。但是此种解法是正解,网上使用Floyd + Spfa得了100分。
代码:
import java.util.Scanner; import static java.lang.Math.*; public class Main{ final int Max_N = 505; final long INF = (long)1e9; int n; long shortest = -1; long[][] low = new long[Max_N][Max_N]; long[][] high = new long[Max_N][Max_N]; void run(){ Scanner in = new Scanner(System.in); n = in.nextInt(); int m = in.nextInt(); in.nextLine(); //初始化 for(int i = 0; i < n; i++){ for(int j = 0; j < n;j++){ low[i][j] = low[j][i] = (long)1e9; high[i][j] = high[j][i] = (long)1e9; } low[i][i] = high[i][i] = 0; } for(int i = 0;i < m; i++){ int t = in.nextInt(); int a = in.nextInt(); int b = in.nextInt(); int c = in.nextInt(); in.nextLine(); if(t == 1){ if(low[a - 1][b - 1] > c) low[a-1][b-1] = low[b-1][a-1] = c; }else{ if(high[a - 1][b - 1] > c) high[a-1][b-1] = high[b-1][a-1] = c; } } in.close(); floyd(); dijkstra(0); System.out.println(shortest); } void dijkstra(int s){ long[] d_low = new long[n]; long[] d_high = new long[n]; for(int i = 0;i < n; i++){ // 分别初始化距离数组。 if(low[s][i] < INF){ d_low[i] = low[s][i] * low[s][i]; }else{ d_low[i] = low[s][i]; } d_high[i] = high[s][i]; } boolean[] isVisit = new boolean[n]; isVisit[s] = true; for(int i = 0; i < n; i++){ int u = -1;long m = INF; for(int j = 0; j < n; j++){//找下一个优化的点。 if(!isVisit[j] && d_high[j] < m){ m = d_high[j]; u = j; } } if(u == -1 || u == n - 1) { shortest = min(d_low[n - 1], d_high[n - 1]); return ; } isVisit[u] = true; m = min(d_low[u], d_high[u]); //走小路到这里更近吗? for(int j = 0; j < n; j++){//更新邻接点。 if(!isVisit[j] && m + high[u][j] < d_high[j]){ d_high[j] = m + high[u][j]; } if(!isVisit[j] && low[u][j] < INF && d_high[u] + low[u][j] * low[u][j] < d_low[j]){//走大道更近吗? d_low[j] = d_high[u] + low[u][j] * low[u][j]; } } } } void floyd(){ for(int k = 0; k < n; k++) for(int i = 0; i < n; i++) for(int j = 0; j <n; j++) low[i][j] = min(low[i][j], low[i][k] + low[k][j]); } public static void main(String[] args) { new Main().run(); } }
上述代码参考其他人的代码,存在部分未知错误。
正解: https://blog.csdn.net/qq_36172505/article/details/81324997