这里有两个单独的问题:找到具有正确数字和的数字之间的最小数字,以及找到具有该数字和的范围内的值的数量。我将分别讨论这些问题。
用数字和 S 计算 A 和 B 之间的值。
解决这个问题的一般方法如下:
- 用数字和 S 计算小于或等于 A - 1 的值的数量。
- 用数字和 S 计算小于或等于 B 的值的个数。
- 从第二个数字中减去第一个数字。
为此,我们应该能够使用动态编程方法。我们将尝试回答以下形式的查询:
有多少个D位数字,第一个数字是k,谁的数字总和是S?
我们将创建一个表 N[D, k, S] 来保存这些值。我们知道 D 最多为 16,S 最多为 136,因此该表将只有 10 × 16 × 136 = 21,760 个条目,这还不错。要填写它,我们可以使用以下基本情况:
- N[1, S, S] = 1 表示 0 ≤ S ≤ 9,因为只有一个一位数的总和小于 10。
- N[1, k, S] = 0 for 0 ≤ S ≤ 9 if k ≠ S,因为第一个数字不是特定总和的一位数的总和不会达到某个值。
- 对于 10 ≤ S ≤ 135,N[1, k, S] = 0,因为对于任何大于一位数的 k,没有一位数的总和恰好等于 S。
- 对于任何 S
然后,我们可以使用以下逻辑来填充其他表项:
- N[D + 1, k, S] = sum(i from 0 to 9) N[D, i, S - k]。
这表示第一个数字为 k 且总和为 S 的 (D+1) 位数字的数量由总和为 S - k 的 D 位数字的数量给出。总和为 S - k 的 D 位数字的数量由总和为 S - k 的第一个数字是 0、1、2、...、9 的 D 位数字的数量给出,所以我们有总结一下。
填写这个 DP 表只需要 O(1) 的时间,事实上,如果你真的很关心时间,你可以预计算它并将它硬编码到程序中。
那么我们如何使用这个表呢?好吧,假设我们想知道与 S 相加的数字有多少小于或等于某个数字 X。为此,我们可以一次处理一个 X 的数字。让我们一次将 X 写为 d1 ... dn。我们可以从 N[n, d1, S] 开始。这为我们提供了第一个数字为 d1 且总和为 S 的 n 位数字的数量。这可能会高估小于或等于 X 且总和为 S 的值的数量。例如,如果我们的数字是 21,111,并且我们想要总和正好为 12 的值的数量,那么查找此表值将为我们提供像 29,100 这样以 2 开头且长度为五位的数字的误报,但仍然是大于 X。为了解决这个问题,我们可以移动到数字 X 的下一位数字。由于第一位数字是 2,所以该数字中的其余数字总和必须为 10。此外,由于 X 的下一位数字(21,111) 是 1,我们现在可以从总数中减去以 2、3、4、5、...、9 开头的 4 位数字,加起来为 10。然后我们可以重复这个过程一位数一次。
更一般地说,我们的算法如下。让 X 是我们的数字, S 是目标总和。写 X = d1d2...dn 并计算以下内容:
# Begin by starting with all numbers whose first digit is less than d[1].
# Those count as well.
result = 0
for i from 0 to d[1]:
result += N[n, i, S]
# Now, exclude everything whose first digit is d[1] that is too large.
S -= d[1]
for i = 2 to n:
for j = d[i] to 8:
result -= N[n, d[i], S]
S -= d[i]
result 的值将是小于或等于 X 且总和正好为 S 的值的数量。该算法最多只能运行 16 次迭代,因此它应该非常快。此外,使用这个算法和前面的减法技巧,我们可以用它来计算 A 和 B 之间有多少个值加起来正好是 S。
求 [A, B] 中数字和 S 的最小值。
我们可以对我们的 DP 表使用类似的技巧来找到大于 A 且总和正好为 S 的最小数字。我将把细节留作练习,但作为提示,一次计算一个数字,试图找到 DP 表返回非零值的最小数字。
希望这会有所帮助!