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

📅  最后修改于: 2021-04-24 14:54:55             🧑  作者: 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的序列,因此在第3位,我们不能在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
    // positon 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
    // positon 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 positon 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
    // positon 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


输出:
5


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