题目:
试题名称:行车路线
时间限制:1.0s
内存限制:256.0MB
问题描述
小明和小芳出去乡村玩,小明负责开车,小芳来导航。
小芳将可能的道路分为大道和小道。大道比较好走,每走1公里小明会增加1的疲劳度。小道不好走,如果连续走小道,小明的疲劳值会快速增加,连续走 s公里小明会增加 s 2的疲劳度。
例如:有5个路口,1号路口到2号路口为小道,2号路口到3号路口为小道,3号路口到4号路口为大道,4号路口到5号路口为小道,相邻路口之间的距离都是2公里。如果小明从1号路口到5号路口,则总疲劳值为(2+2) 2+2+2 2=16+2+4=22。
现在小芳拿到了地图,请帮助她规划一个开车的路线,使得按这个路线开车小明的疲劳度最小。
小芳将可能的道路分为大道和小道。大道比较好走,每走1公里小明会增加1的疲劳度。小道不好走,如果连续走小道,小明的疲劳值会快速增加,连续走 s公里小明会增加 s 2的疲劳度。
例如:有5个路口,1号路口到2号路口为小道,2号路口到3号路口为小道,3号路口到4号路口为大道,4号路口到5号路口为小道,相邻路口之间的距离都是2公里。如果小明从1号路口到5号路口,则总疲劳值为(2+2) 2+2+2 2=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,疲劳度为5
2=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 ≤ 10 5,1 ≤ a, b ≤ n, t是0或1, c ≤ 10 5。保证答案不超过10 6。
对于另外20%的评测用例,不存在小道;
对于另外20%的评测用例,所有的小道不相交;
对于所有评测用例,1 ≤ n ≤ 500,1 ≤ m ≤ 10 5,1 ≤ a, b ≤ n, t是0或1, c ≤ 10 5。保证答案不超过10 6。
解法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