1449. 数位成本和为目标值的最大数字

考点

  • 完全背包
  • 试填法
  • 贪心

思路

完全背包的精髓题,必刷

我先证明一下完全背包的转移本质,数学上的严谨推导

完全背包

要得到最大数字,要保证位数尽可能大;若位数相同,保证字典序大

先用完全背包先求出 f[t] = 凑出总价 t 的最大位数,转移(允许无限用某个数字 i):

1
2
f[0] = 0
f[t] = max( f[t - cost[i]] + 1 ) (t ≥ cost[i])

不可达记为 -INF

复原答案时,逆向考虑DP,并从高位到低位、从 9 到 1 尝试放数字 i: 只要 t >= cost[i]f[t] == f[t - cost[i]] + 1,就说明放一枚 i不会降低“最大位数”,于是就放它,并令 t -= cost[i];继续尝试同一个 i(因为是完全背包)。

这样得到的字符串必定数值最大:先保证位数最优,再尽可能让高位用更大的数字。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Solution {
public:
string largestNumber(vector<int>& cost, int target) {
// f[i][j]: 考虑前i个数, 刚好到j的容量的位数,取最大值
int f[10][5005];
memset(f, 0xcf, sizeof(f));
f[0][0] = 0;
int n = cost.size();
for (int i = 1; i <= n; ++i) {
int x = cost[i - 1];
for (int j = 0; j <= target; ++j) {
if (f[i - 1][j] >= 0) f[i][j] = max(f[i][j], f[i - 1][j]);
if (j >= x && f[i][j - x] >= 0) f[i][j] = max(f[i][j], f[i][j - x] + 1);
}
}
if (f[n][target] < 0) return "0";
// 试填法
int t = target;
string ans;
for (int i = 9; i >= 1 && t; --i) {
int x = cost[i - 1];
while (t >= x && f[i][t - x] + 1 == f[i][t]) {
// 说明有一个i包含在最优解,可以选
ans += (char)(i + '0');
t -= x;
}
}
return ans;
}
};

递推

由于数据范围小,可以直接用递推暴力枚举,省下一维

需要重复一遍,大多数情况下的DP逆向是不可以压缩的,因为这样会覆盖历史情况

这里递推的式子长得很像上面的完全背包压缩后的情况,但完全不是一个东西,所以可以用一维

定义:

1
f[t] = 在可以使用数字 1..9(不限次数)时,凑出总代价 t 的最大位数

递推是数学上直接定义出来的\[ f[t] = \max_{d=1..9,\ t \ge cost[d]} \{ f[t - cost[d]] + 1 \}, \quad f[0]=0 \] 得到一维的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Solution {
public:
string largestNumber(vector<int>& cost, int target) {
int f[5005];
memset(f, 0xcf, sizeof(f));
f[0] = 0;
for (int i = 1; i <= target; ++i) {
for (int j = 1; j <= 9; ++j) {
if (i >= cost[j - 1] && f[i - cost[j - 1]] != 0xcfcfcfcf)
f[i] = max(f[i], f[i - cost[j - 1]] + 1);
}
}
if (f[target] == 0xcfcfcfcf) return "0";
// 试填法
int t = target;
string ans;
for (int i = 9; i >= 1 && t; --i) {
int x = cost[i - 1];
while (t >= x && f[t - x] + 1 == f[t]) {
// 说明有一个i包含在最优解,可以选
ans += (char)(i + '0');
t -= x;
}
}
return ans;
}
};