目录
题目链接:1.蜗牛 - 蓝桥云课 (lanqiao.cn)
思路
暴力贪心
代码实不了现
动态规划
代码实现
难点解释
总结
题目链接:1.蜗牛 - 蓝桥云课 (lanqiao.cn)
思路
暴力贪心
蓝桥杯反正是能暴力出来一个用例是一个,我真的被折磨了好久,自以为自己的代码已经天衣无缝了,但是其实还是有一个致命的错误。那就是我这里采用的是贪心算法的思路,而没有采用动态规划的思路,我来说说为什么这里使用贪心算法的话会出现问题。
我这里的代码思路就是每一个竹竿都判断到底是走下面直接走过来时间短还是使用传送门的时间短,这贪心的写法有一个致命的缺陷就是,如果我是采用的传送门来到的当前竹竿(传过来时传送门就在y=0的地方),然后这个传送到了这个竹竿的比如y=的地方,正好这个竹竿传送到下一个竹竿的传送门在y=0的地方,然后如果我使用走到下一个竹竿只需要1米,那么我采用传送门是不是要先走完y=再走一米,谁更快,这就是问题。
代码如下(能过5%的用例,拿分就行):
代码实不了现
import java.util.Scanner;
// 1:无需package
// 2: 类名必须Main, 不可修改
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 接收参数
// 正整数n,也是竹竿数组的长度
int n = scanner.nextInt();
// 竹竿位置存入数组,这里是按着索引挨个排列的
int[] zgArr = new int[n];
for (int i = 0; i < n; i++) {
zgArr[i] = scanner.nextInt();
}
// 存入传送门的位置,n-1也是传送门数组的长度
int[] csArrStart = new int[n - 1];
int[] csArrEnd = new int[n - 1];
for (int i = 0; i < n - 1; i++) {
// 记录传送门在竹竿上的高度
// 记录起始位置
csArrStart[i] = scanner.nextInt();
// 记录传送到的位置
csArrEnd[i] = scanner.nextInt();
}
// 数据已经全部收到了,准备进入代码
// 总体时间的花费
// 这里初始化到第一根竹竿要花费一点时间
double allTime = zgArr[0];
// 记录一下当前的位置
int nowX = zgArr[0];
int nowY = 0;
for (int i = 0; i < n - 1; i++) {
if (i == n - 2) {
// 直接走过去花费的时间
double time1 = nowY / 1.3 + zgArr[i + 1] - nowX;
//使用传送门花费的时间
double time2 = 0;
// a1 记录当前竹竿传送门的高度
int a1 = csArrStart[i];
if (nowY == a1) {
// 已经在传送门的位置
time2 += 0;
}else if (nowY > a1) {
// 在传送门的上面,往下走v=1.3
time2 += (nowY - a1)/1.3;
}else {
// 在传送门的下面,往上走v=0.7
time2 += (a1 - nowY)/0.7;
}
// 加上从传送门上下来到 y=0 位置花费的时间
time2 += csArrEnd[i] / 1.3;
allTime += Math.min(time1,time2);
break;
}
// 竹竿之间的距离
int dis = zgArr[i + 1] - nowX;
// 如果直接走过去花费时间为
double time1 = 0;
if (nowY == 0) {
time1 = dis;
}else {
// 说明现在在竹竿上,我们要先下去,然后再走过去
time1 = nowY / 1.3 + dis;
}
//如果使用传送门
double time2 = 0;
// 要先走到传送门的位置
// 获取该竹竿的传送门和下一个竹竿的传送到的高度
int a1 = csArrStart[i];
int b1 = csArrEnd[i];
if (nowY == a1) {
// 已经在传送门的位置
time2 = 0;
}else if (nowY > a1) {
// 在传送门的上面,往下走v=1.3
time2 = (nowY - a1)/1.3;
}else {
// 在传送门的下面,往上走v=0.7
time2 = (a1 - nowY)/0.7;
}
if (time1 < time2) {
// 直接走更快
allTime += time1;
// 更新坐标
nowX = zgArr[i + 1];
nowY = 0;
}else {
// 传送更快
allTime += time2;
nowX = zgArr[i + 1];
nowY = b1;
}
}
scanner.close();
System.out.printf("%.2f", allTime);
}
}
动态规划
前面也是说了为什么我使用的贪心算法导致了问题的出错,这时候就只能使用动态规划来解决这个问题了。
首先,我们创建了动态规划数组dp,数组的大小为n+1,其中每个元素dp[i]是一个包含两个元素的数组。dp[i][0]表示到达第i个竹竿底部的最短时间,dp[i][1]表示到达第i个竹竿的传送门位置的最短时间。
当然了,我们需要先初始化一下数据,也就是dp[1][0] = x[1]和dp[1][1] = x[1] + a[1] / 0.7;
我们每次需要找到最短到达第i个竹竿底部的时间和最短到达第i个竹竿上的传送门的时间。
说的详细一点是:我们要找到从dp[i - 1][0] 和 dp[i - 1][1] 中的最短时间再加上路程来到当前竹竿的传送点,以及找到从dp[i - 1][0] 和 dp[i - 1][1] 中的最短时间再加上路程来到当前竹竿的底部。
我们可以得到如下公式:
对于dp[i][1] :
对于dp[i][0] :
这样子就成功的记录了每一种的情况,就不会有我们使用谈心算法的情况而导致的我们的路径其实并不是最短的路径了。
代码实现
import java.util.Scanner;
// 1:无需package
// 2: 类名必须Main, 不可修改
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
// 输入整数n,这个是竹竿的数量
int n = sc.nextInt();
// 存储竹竿的x坐标
int[] x = new int[n + 1];
// 存储 ai
int[] a = new int[n + 1];
// 存储 bi
int[] b = new int[n + 1];
// 将坐标数据存储进数组
for (int i = 1; i <= n; i++) {
x[i] = sc.nextInt();
}
// 将传送门的位置数据存入数组
for (int i = 1; i < n; i++) {
a[i] = sc.nextInt();
b[i + 1] = sc.nextInt();
}
// 设置dp数组
double[][] dp = new double[n + 1][2];
// 初始化到第一个竹竿底部的时间
dp[1][0] = x[1];
// 初始化直接到第一个竹竿的传送门的时间
dp[1][1] = x[1] + a[1] / 0.7;
// 进入动态规划核心代码
for (int i = 2; i <= n; i++) {
// 先计算到竹竿传送门的时间
if (a[i] > b[i]) {
dp[i][1] = Math.min(dp[i - 1][0] + x[i] - x[i - 1] + a[i] / 0.7, dp[i - 1][1] + (a[i] - b[i]) / 0.7);
} else {
dp[i][1] = Math.min(dp[i - 1][0] + x[i] - x[i - 1] + a[i] / 0.7, dp[i - 1][1] + (b[i] - a[i]) / 1.3);
}
// 再计算到竹竿底部的时间
dp[i][0] = Math.min(dp[i - 1][1] + b[i] / 1.3, dp[i - 1][0] + x[i] - x[i - 1]);
}
System.out.printf("%.2f", dp[n][0]);
sc.close();
}
}
动态规划解决问题,你就说嗨皮不嗨皮就完事啦!!!
难点解释
动态规划的主体部分,对第2个到第n个竹竿进行操作。这个for循环主要解决的问题是,每到一个新的竹竿,有两种可能的状态:一个是在竹竿上,不需要使用传送门;另一个是在传送门上。对于每种状态,有多种可能的情况,所以我们需要计算每种情况的时间,然后选择时间最短的那种。
现在让我们看一下这段代码的具体操作:
dp[i][0] = Math.min(dp[i-1][1] +b[i]/1.3 ,dp[i-1][0]+x[i]-x[i-1]);
这行代码表示,现在你已经到达了第i个竹竿,你可以选择从上一个竹竿直接爬过来,耗费的时间就是dp[i-1][0](上一个竹竿的时间)+ x[i]-x[i-1](爬过来的时间)。你还可以选择从传送门过来,耗费的时间就是dp[i-1][1](上一个传送门的时间)+ b[i] / 1.3(传送门下来耗费的时间)。然后比较这两种方式,选择一种时间较短的,存入dp[i][0]。
if(a[i]>b[i]){
dp[i][1] = Math.min(dp[i-1][0] + x[i]-x[i-1] + a[i]/0.7, dp[i-1][1] + (a[i]-b[i])/0.7);
}else {
dp[i][1] = Math.min(dp[i-1][0] + x[i]-x[i-1] + a[i]/0.7,dp[i-1][1] + (b[i]-a[i]) /1.3);
}
对于dp[i][1],同样有两种情况。以第一种情况为例,在a[i] > b[i]时,你可以选择从上一个竹竿爬到传送门,然后通过传送门到达当前的竹竿,耗费的时间是dp[i-1][0] + x[i]-x[i-1] + a[i] / 0.7。也可以选择先从上一个传送门爬过来,然后走到当前竹竿的传送门,耗费的时间是dp[i-1][1] + (a[i]-b[i]) / 0.7。比较这两种方式,选择一种时间较短的,存入dp[i][1]。
总结
动态规划我学不了一点点,马上又要崩溃了。