📜  数字DP |介绍

📅  最后修改于: 2021-05-04 12:25:50             🧑  作者: Mango

先决条件:如何解决动态编程问题?

有许多类型的问题要求对两个整数(例如“ 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的整数。

示例问题:
给定两个整数ab 。您的任务是打印
所有数字均出现在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上添加更多问题。