【01 dp】B004_LC_最低票价(暴搜 / 记忆化搜索 / 01 dp)

一、Problem

在一个火车旅行很受欢迎的国度,你提前一年计划了一些火车旅行。在接下来的一年里,你要旅行的日子将以一个名为 days 的数组给出。每一项是一个从 1 到 365 的整数。火车票有三种不同的销售方式:

  • 一张为期一天的通行证售价为 costs[0] 美元;
  • 一张为期七天的通行证售价为 costs[1] 美元;
  • 一张为期三十天的通行证售价为 costs[2] 美元。

通行证允许数天无限制的旅行。 例如,如果我们在第 2 天获得一张为期 7 天的通行证,那么我们可以连着旅行 7 天:第 2 天、第 3 天、第 4 天、第 5 天、第 6 天、第 7 天和第 8 天。

返回你想要完成在给定的列表 days 中列出的每一天的旅行所需要的最低消费。

输入:days = [1,4,6,7,8,20], costs = [2,7,15]
输出:11
解释: 
例如,这里有一种购买通行证的方法,可以让你完成你的旅行计划:
在第 1 天,你花了 costs[0] = $2 买了一张为期 1 天的通行证,它将在第 1 天生效。
在第 3 天,你花了 costs[1] = $7 买了一张为期 7 天的通行证,它将在第 3, 4, ..., 9 天生效。
在第 20 天,你花了 costs[0] = $2 买了一张为期 1 天的通行证,它将在第 20 天生效。
你总共花了 $11,并完成了你计划的每一天旅行。

二、Solution

方法一:暴搜

  • 对于第 cur 天,它可能是备计划的,也可能是不计划被的,但都可以去买票:
  • 在第 cur 天,我们可以做出以下选择:
    • 不买任何票
    • 买可持续 1 天的票
    • 买可持续 7 天的票
    • 买可持续 30 天的票
  • 每次到第 end 天结算以下即可…
class Solution {
	int min = 0x3f3f3f3f, end, ds[], cs[], a[] = new int[] {1, 7, 30};
	boolean[] plan;
	void dfs(int cur, int cost) {
		if (cur > end) {
			min = Math.min(min, cost);
			return;
		}
		for (int i = 0; i < 3; i++) {
			if (!plan[cur]) {
				dfs(cur+1, cost);
                continue;
			}
			dfs(cur+a[i], cost+cs[i]);
		}
	}
    public int mincostTickets(int[] days, int[] costs) {
        this.ds = days; this.cs = costs; this.end = days[days.length-1];
		plan = new boolean[366];
		for (int d : days) plan[d] = true;
		dfs(0, 0);
		return min;
    }
}

这样做竟然只过了 1/66 样例…,剪下枝吧:

  • 当当前花费高于历史花费,return 皆可
  • 再加入 int[] f 记录某一天的最小花费,当下一次在遍历到这一天时,如果当前花费 cost >= f[cur] 的话,也可 return
class Solution {
public:
	int mi = INT_MAX, plan[367], s[3] = {1, 7, 30};
	vector<int> ds, cs;
    
	void dfs(int d, int c, vector<int>& f) {
		if (d > ds.back()) {
			mi = min(mi, c);
			return;
		}
        if (c > mi) 
        	return;
        if (f[d] != 0 && f[d] <= c) 
        	return;
        f[d] = c;
		for (int i = 0; i < 3; i++) {
			if (!plan[d]) dfs(d+1, c, f);
			else          dfs(d+s[i], c+cs[i], f);
		}
	}
    int mincostTickets(vector<int>& days, vector<int>& costs) {
    	ds = days; cs = costs;
    	for (int d : ds) plan[d] = 1;
        vector<int> f(ds.back()+5, 0);
    	dfs(0, 0, f);
    	return mi;
    }
};

嗯,32 ms 效率并不是很高,但可 ac…

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( n ) O(n) O(n)

方法二:dp

  • 定义状态
    • d p [ i ] dp[i] dp[i] 表示第 i 天最小花费
  • 思考状态转移方程
    • d p [ i ] = d p [ i − 1 ] dp[i] = dp[i-1] dp[i]=dp[i1],当天没有安排
    • d p [ i ] = m i n ( o n e , s e v , t h i ) dp[i] = min(one, sev, thi) dp[i]=min(one,sev,thi),表示第 i i i 天的花费,取决于前面的三种消费:
      • 前一天买了 1 1 1 天的票,撑到了到现在
      • 前七天买了 7 7 7 天的票,撑到了到现在
      • 前三十天买了 30 30 30 天的票,撑到了到现在
  • 思考初始化:
    • d p [ 0 ] = 0 dp[0] = 0 dp[0]=0
  • 思考输出 d p [ l a s t ] dp[last] dp[last]
class Solution {
public:
    int plan[367];
    int mincostTickets(vector<int>& ds, vector<int>& cs) {
		int d[3] = {1, 7, 30}, n = ds.size(), e = ds[n-1];
    	for (int i : ds) plan[i] = 1;
        vector<int> f(e+5, 0);
        
    	for (int i = 1; i <= e; i++) {
			if (!plan[i])
                f[i] = f[i-1];
			else {
				int a = f[i-1] + cs[0];
                int b = f[max(0, i-7)] + cs[1];
                int c = f[max(0, i-30)] + cs[2];
				f[i] = min({a, b, c});
            }
		}
    	return f[e];
    }
};

实在搞不懂为什么 else 里面写循环 for (3) 循环不行的…

int mi = INF;
for (int j = 0; j < 3; j++) if (i-d[j] >= 0) {
    mi = min(mi, f[i-d[j]] + cs[j]);
}
f[i] = mi;

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( n ) O(n) O(n)

你可能感兴趣的:(#,背包问题)