今天带来的是AI刷题的一个题目:补给站最优花费问题。
1、首先看题目:
我们需要提取出几个关键信息--->题目会给一个信息旅行的总天数M--->每天都会消耗一份食物--->旅行途中会经过N个补给站--->N个补给站出现在不同天数且不保证每天都出现一个--->第A天的补给站会有一个价格为B元的食物--->保证第0天会有一个补给站(即一开始没有初始食物,一定需要购买)--->补给站按顺序给出(方便了我们后面的算法设计)
题目需要求:顺利完成这个旅程的最小花费是多少?
2、然后我们再思考:
- 每天是否只能买一份?答案:否;
- 是否有完成不了这个旅程的情况?答案:否,因为题目没有限制我们所拥有的金钱数;
3、接着再次看题目给的样例:
思考这个答案是怎么来的?我们可以在纸上画一画,看一下答案:7 是哪一种组合。
如果你去把所有的组合列出来了,你就会发现答案的组合是:购买了第0天的2次+购买了第2天的三次=2 * 2 + 3 * 1 --->7
我们再次尝试去思考是怎么得到这个答案的???首先我们可以确定这个题目是一个组合问题,那我们就需要尝试一下看看能不能用动态规划来实现一下这一道题
用动态规划,那就是一个痛苦的过程需要我们去找到题目的状态转移方程来实现。这个很抽象。但是这道题的状态转移方程不难。我们可以把我们自己带入这个题目试一下:
假如给你来规划这一个旅途,你知道这一路上会在哪一天出现补给站,那现在你应该在哪些补给站买?买多少?
我们肯定会想从最便宜的补给站买最多的食物以便可以完成这个旅途!但是你不一定一开始就可以到达这一个最便宜的补给站。那你肯定要想办法用最少的花费到达其次便宜的补给站;而到达这个其次便宜的补给站,你又需要再次用最少的花费到达这个点,以此类推,这个类推的过程就可以抽象成动态规划的转移过程。
这样我们是不是可以想到:
- 用一个
dp[i]来表示到达第i天的最少花费 - 得到第i天的最少花费
dp[i]需要一个一个遍历前面经过的所有补给站p[j](用p[j]来表示第j个补给站) - 然后通过
dp[p[j][0]]来得到之前到达这个补给站的最小花费,这个在第i天时肯定是已经计算出来的,因为是一层一层往第i天推的 - 然后假设从这个补给站买食物一直维持到第i天
- 即
dp[p[j][0]]+(i-p[j][0])*p[j][1] - 然后对第i天所经过的所有补给站来一次假设,取最小值
- 即
dp[i]=min(dp[i],dp[p[j][0]]+(i-p[j][0])*p[j][1] - 遍历完前面的补给站,最后的dp[i]就是答案
得到状态转移方程也就是上面那一个:
dp[i]=min(dp[i],dp[p[j][0]]+(i-p[j][0])*p[j][1]
思路总结:就是对第i天,我们从前面走过的补给站中找一个最优补给站 这个最优补给站需满足以下条件:
- 在这个补给站购买食物一直到第i天的花费
- 再加上到达要这个补给站花费
- 二者的总费用最少
附上实现代码:
import java.util.*;
public class Main {
public static int solution(int m, int n, int[][] p) {
// 初始化dp数组,长度为m,初始值为无穷大
int[] dp=new int[m+1];
Arrays.fill(dp,100000000);
dp[0]=0;
for(int i=1;i<=m;++i){
for(int j=0;j<n;++j){
if(i-p[j][0]<0) break;
dp[i]=Math.min(dp[i],dp[p[j][0]]+(i-p[j][0])*p[j][1]);
}
}
return dp[m];
}
public static void main(String[] args) {
// Add your test cases here
System.out.println(solution(5, 4, new int[][]{{0, 2}, {1, 3}, {2, 1}, {3, 2}}) == 7);
}
}