📌  相关文章
📜  计算数字总和可被 K 整除的范围内的数字,其中第一位和最后一位数字不同

📅  最后修改于: 2021-09-22 09:47:40             🧑  作者: Mango

给定一个LR形式的范围,以及一个值K ,任务是计算范围LR之间的数字,使得数字的总和可以被K整除,并且第一个数字不等于号码的最后一位。

例子:

天真的方法:
解决这个问题的天真的方法是检查每个数字是否满足给定条件。但是,由于数字的范围,此解决方案将效率低下。

有效的方法:
解决这个问题的主要思想是使用数字动态规划。
需要为转换定义各种状态。因此,我们可以将 DP 状态定义为:

  1. 由于在数字 DP 中,我们将数字视为数字序列,因此为了遍历该序列,我们需要将位置作为状态。我们通常做的是将每个可能的数字 [0, 9] 放在递归调用中,我们可以将其放在每个位置以找到可能的解决方案,前提是所有条件都应满足。
  2. 我们需要的第二件事是总和,我们已经建立了我们的序列。由于总和可能很大,因此我们可以将总和保持为求和模 K以获得更好的空间和时间复杂度。所以总和是第二个DP状态。
  3. 我们需要的第三件事是数字,我们已经把它放在第一个位置。在放置序列的最后一位数字时,我们需要检查它是否等于我们之前放置的第一位数字。所以,这将是第三个 DP 状态。
  4. 问题是每次我们创建一个 N 位序列,其中 N 是数字上限中存在的位数,如果 (N=3) 它不会创建 1,但它会创建 001。这里是第一个数字是 0,最后一个数字是 1,但这是错误的,因为我们创建了一个一位数字,它的第一位数字等于最后一位数字。因此,我们必须在递归调用中每次都检查这一点。假设我们必须构建一个像 0002 这样的序列,我们需要将起始数字更新为 2。因此应该忽略零前缀。为了检查这一点,我们需要一个新的布尔状态 (0 或 1) 。这是第四个DP状态。
  5. 我们需要做的最后一件事是检查我们正在构建的数字是否不超过上限。假设我们正在构建一个小于等于 456 的数字。我们创建了一个类似 45 的序列,所以在第三位,我们不能在 0 到 9 之间放置任何数字。这里我们只能放置从 0 到 6 的数字。所以为了检查这个界限,我们需要一个额外的布尔状态。这是我们最后的DP状态。

算法:

  • 在每个位置,我们计算数字总和并删除零前缀。
  • 第一个非零数字将是我们序列的起始数字。
  • 在最后一个位置,我们将检查它是否与起始数字匹配。

下面是上述方法的实现。

C++
// C++ Program to count numbers
// in a range with digit sum
// divisible by K having first
// and last digit different
 
#include 
using namespace std;
#define ll long long int
 
ll K;
ll N;
vector v;
ll dp[20][1000][10][2][2];
 
void init(ll x)
{
    memset(dp, -1, sizeof(dp));
 
    // For calculating the upper
    // bound of the sequence.
    v.clear();
    while (x > 0) {
        v.push_back(x % 10);
        x /= 10;
    }
 
    reverse(v.begin(), v.end());
 
    N = v.size();
}
 
ll fun(ll pos, ll sum, ll st, ll check, ll f)
{
    if (pos == N) {
 
        // checking whether the sum
        // of digits = 0 or not
        // and the corner case
        // as number equal to 1
        return (sum == 0
                and check == 1);
    }
 
    // If the state is visited
    // then return the answer
    // of this state directly.
    if (dp[pos][sum][st][check][f] != -1)
        return dp[pos][sum][st][check][f];
 
    ll lmt = 9;
 
    // for checking whether digit
    // to be placed is up to
    // upper bound at the
    // position pos or upto 9
    if (!f)
        lmt = v[pos];
 
    ll ans = 0;
    for (int digit = 0; digit <= lmt; digit++) {
 
        ll nf = f;
 
        // calculating new digit
        // sum modulo k
        ll new_sum = (sum + digit) % K;
 
        ll new_check = check;
        ll new_st = st;
        if (f == 0 and digit < lmt)
            nf = 1;
 
        // check if there is a prefix
        // of 0s and current digit != 0
        if (check == 0 and digit != 0) {
 
            // Then current digit will
            // be the starting digit
            new_st = digit;
 
            // Update the boolean flag
            // that the starting digit
            // has been found
            new_check = 1;
        }
 
        // At n-1, check if current digit
        // and starting digit are the same
        // then no need to calculate this answer.
        if (pos == N - 1
            and new_st == digit)
            continue;
 
        // Else find the answer
        ans += fun(pos + 1,
                   new_sum,
                   new_st,
                   new_check, nf);
    }
 
    return dp[pos][sum][st][check][f] = ans;
}
 
// Function to find the required count
void findCount(int L, int R, int K)
{
 
    // Setting up the upper bound
    init(R);
 
    // calculating F(R)
    ll r_ans = fun(0, 0, 0, 0, 0);
 
    // Setting up the upper bound
    init(L - 1);
 
    // calculating F(L-1)
    ll l_ans = fun(0, 0, 0, 0, 0);
 
    cout << r_ans - l_ans;
}
 
// Driver code
int main()
{
    ll L = 10;
    ll R = 20;
    K = 2;
 
    findCount(L, R, K);
 
    return 0;
}


Java
// Java Program to count numbers
// in a range with digit sum
// divisible by K having first
// and last digit different
import java.util.*;
 
class GFG{
 
static int K;
static int N;
static Vector v = new Vector<>();
static int [][][][][]dp = new int[20][1000][10][2][2];
 
static void init(int x)
{
    for(int i = 0; i < 20; i++)
        for(int j = 0; j < 1000; j++)
            for(int k = 0; k < 10; k++)
                for(int l = 0; l < 2; l++)
                    for(int m = 0; m < 2; m++)
                        dp[i][j][k][l][m] = -1;
 
    // For calculating the upper
    // bound of the sequence.
    v.clear();
    while (x > 0)
    {
        v.add(x % 10);
        x /= 10;
    }
    Collections.reverse(v);
    N = v.size();
}
 
static int fun(int pos, int sum,
               int st, int check, int f)
{
    if (pos == N)
    {
 
        // checking whether the sum
        // of digits = 0 or not
        // and the corner case
        // as number equal to 1
        return (sum == 0 && check == 1) ? 1 : 0;
    }
 
    // If the state is visited
    // then return the answer
    // of this state directly.
    if (dp[pos][sum][st][check][f] != -1)
        return dp[pos][sum][st][check][f];
 
    int lmt = 9;
 
    // for checking whether digit
    // to be placed is up to
    // upper bound at the
    // position pos or upto 9
    if (f == 0)
        lmt = v.get(pos);
 
    int ans = 0;
    for (int digit = 0; digit <= lmt; digit++)
    {
        int nf = f;
 
        // calculating new digit
        // sum modulo k
        int new_sum = (sum + digit) % K;
 
        int new_check = check;
        int new_st = st;
        if (f == 0 && digit < lmt)
            nf = 1;
 
        // check if there is a prefix
        // of 0s and current digit != 0
        if (check == 0 && digit != 0)
        {
 
            // Then current digit will
            // be the starting digit
            new_st = digit;
 
            // Update the boolean flag
            // that the starting digit
            // has been found
            new_check = 1;
        }
 
        // At n-1, check if current digit
        // and starting digit are the same
        // then no need to calculate this answer.
        if (pos == N - 1 && new_st == digit)
            continue;
 
        // Else find the answer
        ans += fun(pos + 1, new_sum,
                    new_st, new_check, nf);
    }
 
    return dp[pos][sum][st][check][f] = ans;
}
 
// Function to find the required count
static void findCount(int L, int R, int K)
{
 
    // Setting up the upper bound
    init(R);
 
    // calculating F(R)
    int r_ans = fun(0, 0, 0, 0, 0);
 
    // Setting up the upper bound
    init(L - 1);
 
    // calculating F(L-1)
    int l_ans = fun(0, 0, 0, 0, 0);
 
    System.out.print(r_ans - l_ans);
}
 
// Driver code
public static void main(String[] args)
{
    int L = 10;
    int R = 20;
    K = 2;
 
    findCount(L, R, K);
}
}
 
// This code contributed by PrinciRaj1992


Python3
# Python3 program to count numbers
# in a range with digit sum
# divisible by K having first
# and last digit different
K = 0
N = 0
v = []
dp = [[[[[-1 for i in range(2)]
             for j in range(2)]
             for k in range(10)]
             for l in range(1000)]
             for m in range(20)]
 
def init(x):
     
    # For calculating the upper
    # bound of the sequence.
    global K
    global N
    global v
    v = []
     
    while (x > 0):
        v.append(x % 10)
        x //= 10
 
    v = v[::-1]
 
    N = len(v)
 
def fun(pos, sum, st, check, f):
     
    global N
    global v
     
    if (pos == N):
         
        # Checking whether the sum of
        # digits = 0 or not and the
        # corner case as number equal to 1
        return (sum == 0 and check == 1)
 
    # If the state is visited
    # then return the answer
    # of this state directly.
    if (dp[pos][sum][st][check][f] != -1):
        return dp[pos][sum][st][check][f]
 
    lmt = 9
 
    # For checking whether digit to
    # be placed is up to upper bound
    # at the position pos or upto 9
    if (f == False):
        lmt = v[pos]
 
    ans = 0
    for digit in range(lmt + 1):
        nf = f
 
        # Calculating new digit
        # sum modulo k
        new_sum = (sum + digit) % K
 
        new_check = check
        new_st = st
         
        if (f == 0 and digit < lmt):
            nf = 1
 
        # Check if there is a prefix
        # of 0s and current digit != 0
        if (check == 0 and digit != 0):
             
            # Then current digit will
            # be the starting digit
            new_st = digit
 
            # Update the boolean flag
            # that the starting digit
            # has been found
            new_check = 1
 
        # At n-1, check if current digit
        # and starting digit are the same
        # then no need to calculate this answer
        if (pos == N - 1 and new_st == digit):
            continue
 
        # Else find the answer
        ans += fun(pos + 1, new_sum,
                   new_st, new_check, nf)
     
        dp[pos][sum][st][check][f] = ans
 
    return ans
 
# Function to find the required count
def findCount(L, R, K):
     
    # Setting up the upper bound
    init(R)
 
    # calculating F(R)
    r_ans = fun(0, 0, 0, 0, 0)
 
    # Setting up the upper bound
    init(L - 1)
 
    # Calculating F(L-1)
    r_ans = 0
    l_ans = fun(0, 0, 0, 0, 0)
 
    print(l_ans - r_ans)
 
# Driver code
if __name__ == '__main__':
     
    L = 10
    R = 20
    K = 2
 
    findCount(L, R, K)
 
# This code is contributed by Surendra_Gangwar


C#
// C# program to count numbers
// in a range with digit sum
// divisible by K having first
// and last digit different
using System;
using System.Collections.Generic;
 
class GFG{
 
static int K;
static int N;
static List v = new List();
static int [,,,,]dp = new int[20, 1000, 10, 2, 2];
 
static void init(int x)
{
    for(int i = 0; i < 20; i++)
       for(int j = 0; j < 1000; j++)
          for(int k = 0; k < 10; k++)
             for(int l = 0; l < 2; l++)
                for(int m = 0; m < 2; m++)
                   dp[i, j, k, l, m] = -1;
 
    // For calculating the upper
    // bound of the sequence.
    v.Clear();
    while (x > 0)
    {
        v.Add(x % 10);
        x /= 10;
    }
    v.Reverse();
    N = v.Count;
}
 
static int fun(int pos, int sum, int st, 
               int check, int f)
{
    if (pos == N)
    {
 
        // Checking whether the sum
        // of digits = 0 or not
        // and the corner case
        // as number equal to 1
        return (sum == 0 && check == 1) ? 1 : 0;
    }
 
    // If the state is visited
    // then return the answer
    // of this state directly.
    if (dp[pos, sum, st, check, f] != -1)
        return dp[pos, sum, st, check, f];
 
    int lmt = 9;
 
    // For checking whether digit
    // to be placed is up to
    // upper bound at the
    // position pos or upto 9
    if (f == 0)
        lmt = v[pos];
 
    int ans = 0;
    for(int digit = 0; digit <= lmt; digit++)
    {
       int nf = f;
        
       // Calculating new digit
       // sum modulo k
       int new_sum = (sum + digit) % K;
       int new_check = check;
       int new_st = st;
        
       if (f == 0 && digit < lmt)
           nf = 1;
        
       // Check if there is a prefix
       // of 0s and current digit != 0
       if (check == 0 && digit != 0)
       {
            
           // Then current digit will
           // be the starting digit
           new_st = digit;
            
           // Update the bool flag
           // that the starting digit
           // has been found
           new_check = 1;
       }
        
       // At n-1, check if current digit
       // and starting digit are the same
       // then no need to calculate this answer.
       if (pos == N - 1 && new_st == digit)
           continue;
        
       // Else find the answer
       ans += fun(pos + 1, new_sum,
                  new_st, new_check, nf);
    }
    return dp[pos, sum, st, check, f] = ans;
}
 
// Function to find the required count
static void findCount(int L, int R, int K)
{
 
    // Setting up the upper bound
    init(R);
 
    // Calculating F(R)
    int r_ans = fun(0, 0, 0, 0, 0);
 
    // Setting up the upper bound
    init(L - 1);
 
    // calculating F(L-1)
    int l_ans = fun(0, 0, 0, 0, 0);
 
    Console.Write(r_ans - l_ans);
}
 
// Driver code
public static void Main(String[] args)
{
    int L = 10;
    int R = 20;
    K = 2;
 
    findCount(L, R, K);
}
}
 
// This code is contributed by 29AjayKumar


Javascript


输出:
5

时间复杂度: O(18*1000*10*2*2),接近 O(2*10^6)