先决条件:如何解决动态规划问题?
有许多类型的问题要求计算两个整数之间的整数“x ”的数量,比如“a ”和“ b ”,这样 x 满足可以与其数字相关的特定属性。
因此,如果我们说G(x)表示 1 到 x(包括)之间的此类整数的数量,那么 a 和 b 之间的此类整数的数量可以由G(b) – G(a-1) 给出。这是 Digit DP (动态编程)发挥作用的时候。所有满足上述性质的整数计数问题都可以通过数字DP方法解决。
关键概念
- 让给定的数字 x 有 n 位数字。数字 DP 的主要思想是首先将数字表示为数字 t[] 的数组。假设 a 我们有t n t n-1 t n-2 … t 2 t 1作为十进制表示,其中t i (0 < i <= n)表示从右边开始的第 i 个数字。最左边的数字t n是最高有效数字。
- 现在,在以这种方式表示给定数字后,我们生成小于给定数字的数字,并同时使用 DP 计算,如果数字满足给定属性。我们开始生成位数 = 1 的整数,然后直到位数 = n。可以通过将最左边的数字设置为零来分析位数少于 n 的整数。
示例问题:
给定两个整数a和b 。你的任务是打印总和
出现在 a 和 b 之间的整数中的所有数字。
例如,如果 a = 5 且 b = 11,则答案为 38 (5 + 6 + 7 + 8 + 9 + 1 + 0 + 1 + 1)
约束条件:1 <= a < b <= 10^18
现在我们看到,如果我们已经计算了具有 n-1 个数字的状态的答案,即t n-1 t n-2 … t 2 t 1并且我们需要计算具有 n 个数字的状态的答案t n t n-1 t n-2 … t 2 t 1 。所以,很明显,我们可以使用前一个状态的结果而不是重新计算它。因此,它遵循重叠属性。
让我们想想这个DP的状态
我们的 DP 状态将是dp(idx,tight, sum)
1) idx
- 它讲述给定整数中从右边开始的索引值
2)紧
- 这将说明当前数字范围是否受到限制。如果当前数字的
范围不受限制,那么它将跨越从 0 到 9(包括)否则它将跨越
从 0 到 digit[idx](包括)。
示例:考虑我们的极限整数是 3245,我们需要计算 G(3245)
指数:4 3 2 1
数字:3 2 4 5
无限制范围:
现在假设到目前为止生成的整数是: 3 1 * * ( * 是空的地方,其中数字将被插入以形成整数)。
index : 4 3 2 1
digits : 3 2 4 5
generated integer: 3 1 _ _
在这里,我们看到索引 2 具有不受限制的范围。现在索引 2 可以包含从 0 到 9(包括)范围内的数字。
对于不受限制的范围, tight = 0
限制范围:
现在假设到目前为止生成的整数是: 3 2 * * (’*’ 是一个空的地方,数字将被插入以形成整数)。
index : 4 3 2 1
digits : 3 2 4 5
generated integer: 3 2 _ _
在这里,我们看到索引 2 有一个限制范围。现在索引 2 只能包含 0 到 4(包括)范围内的数字
对于限制范围紧= 1
3) 总和
- 此参数将存储从 msd 到 idx 生成的整数中的数字总和。
- 该参数和的最大值可以是 9*18 = 162,考虑整数中的 18 位
状态关系
状态关系的基本思想非常简单。我们以自上而下的方式制定 dp。
假设我们在具有索引 idx的 msd。所以最初总和将为 0。
因此,我们将用其范围内的数字填充索引处的数字。假设它的范围是从 0 到 k(k<=9,取决于紧值)并从下一个状态获取答案,其中索引 = idx-1 和 sum = 前一个 sum + 选择的数字。
int ans = 0;
for (int i=0; i<=k; i++) {
ans += state(idx-1, newTight, sum+i)
}
state(idx,tight,sum) = ans;
如何计算 newTight 值?
一个状态的新紧值取决于它之前的状态。如果前一个状态的紧值是 1 并且选择的 idx 处的数字是 digit[idx](即限制整数中 idx 处的数字),那么只有我们新的紧值才会是 1,因为它才告诉到目前为止形成的数字是限制整数的前缀。
// digitTaken is the digit chosen
// digit[idx] is the digit in the limiting
// integer at index idx from right
// previouTight is the tight value form previous
// state
newTight = previousTight & (digitTake == digit[idx])
上述实现的 C++ 代码:
CPP
// Given two integers a and b. The task is to print
// sum of all the digits appearing in the
// integers between a and b
#include "bits/stdc++.h"
using namespace std;
// Memoization for the state results
long long dp[20][180][2];
// Stores the digits in x in a vector digit
long long getDigits(long long x, vector &digit)
{
while (x)
{
digit.push_back(x%10);
x /= 10;
}
}
// Return sum of digits from 1 to integer in
// digit vector
long long digitSum(int idx, int sum, int tight,
vector &digit)
{
// base case
if (idx == -1)
return sum;
// checking if already calculated this state
if (dp[idx][sum][tight] != -1 and tight != 1)
return dp[idx][sum][tight];
long long ret = 0;
// calculating range value
int k = (tight)? digit[idx] : 9;
for (int i = 0; i <= k ; i++)
{
// caclulating newTight value for next state
int newTight = (digit[idx] == i)? tight : 0;
// fetching answer from next state
ret += digitSum(idx-1, sum+i, newTight, digit);
}
if (!tight)
dp[idx][sum][tight] = ret;
return ret;
}
// Returns sum of digits in numbers from a to b.
int rangeDigitSum(int a, int b)
{
// initializing dp with -1
memset(dp, -1, sizeof(dp));
// storing digits of a-1 in digit vector
vector digitA;
getDigits(a-1, digitA);
// Finding sum of digits from 1 to "a-1" which is passed
// as digitA.
long long ans1 = digitSum(digitA.size()-1, 0, 1, digitA);
// Storing digits of b in digit vector
vector digitB;
getDigits(b, digitB);
// Finding sum of digits from 1 to "b" which is passed
// as digitB.
long long ans2 = digitSum(digitB.size()-1, 0, 1, digitB);
return (ans2 - ans1);
}
// driver function to call above function
int main()
{
long long a = 123, b = 1024;
cout << "digit sum for given range : "
<< rangeDigitSum(a, b) << endl;
return 0;
}
输出:
digit sum for given range : 12613