题目:
试题名称:行车路线
时间限制: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