先决条件:如何解决动态编程问题?
有许多类型的问题要求对两个整数(例如“ a ”和“ b ”)之间的整数“ x ”进行计数,以使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个数字。最左边的数字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
现在我们看到,如果我们已经计算出n位数字为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(含0和9)。
对于无限制范围,紧密= 0
限制范围:
现在假设到目前为止生成的整数是:3 2 * *(“ *”是一个空位,将在其中插入数字以形成整数)。
index : 4 3 2 1
digits : 3 2 4 5
generated integer: 3 2 _ _
在这里,我们看到索引2的范围受到限制。现在索引2只能包含0到4(包括0和4)范围内的数字
对于无限制范围,紧密= 1
3)总和
- 此参数将存储生成的整数(从msd到idx)中的位数之和。
- 考虑到整数中的18位数字,此参数总和的最大值可以为9 * 18 = 162
国家关系
状态关系的基本思想非常简单。我们以自上而下的方式制定dp。
假设我们在msd上具有索引idx。因此,最初的总和为0。
因此,我们将使用索引范围内的数字填充索引处的数字。假设它的范围是从0到k(k <= 9,取决于紧密值),并从下一个具有index = 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++代码:
// 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
时间复杂度:
总有idx * sum * tight个状态,我们正在执行0到9次迭代来访问每个状态。因此,时间复杂度将为O(10 * idx * sum * tight) 。在这里,我们观察到对于64位无符号整数,tight = 2和idx可以最大为18,此外,总和最大为9 * 18〜200。因此,总的来说,我们有10 * 18 * 200 * 2〜10 ^ 5次迭代可以在0.01秒内轻松执行。
上面的问题也可以使用简单的递归来解决,而无需任何备注。可以在这里找到上述问题的递归解决方案。我们很快将在以后的帖子中在digit dp上添加更多问题。