📜  数字DP |介绍

📅  最后修改于: 2021-09-22 10:00:07             🧑  作者: Mango

先决条件:如何解决动态规划问题?
有许多类型的问题要求计算两个整数之间的整数“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 的整数。

示例问题:
给定两个整数ab 。你的任务是打印总和
出现在 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