如何计算可被数字总和整除的数字?

时间:2015-03-31 15:16:54

标签: algorithm math optimization

以下是hackerearth提出的问题。 这是问题的链接 problem! 我在java和c中编写了它的解决方案,但是在提交的某些测试用例中超出了时间限制。没有参与者能够为所有测试用例解决这个问题。什么是最有效的解决方案?

问题:

  鲍勃喜欢DSD Numbers。 DSD Number是一个可被其整除的数字   十进制表示中的数字和。

     

digitSum(n):n的数字之和(以十进制表示形式)

     

例如:n = 1234然后digitSum(n)= 1 + 2 + 3 + 4 = 10

     

DSD Number是数字n,n%digitSum(n)等于0

     

Bob要求Alice告诉范围[L,R]中的DSD号码数量   包容。

约束:

  

1< =测试用例< = 50

     

1·; = L< = R&LT = 10 ^ 9

示例输入

4
2 5
1 10
20 45
1 100

示例输出

4
10
9
33

Java代码:

class DSD {

public static void main(String[] args) throws IOException {
    BufferedReader br = new BufferedReader(new    InputStreamReader(System.in));
    PrintWriter out=new PrintWriter(System.out);
    int t=Integer.parseInt(br.readLine());
    while(t-->0){
    StringTokenizer st=new StringTokenizer(br.readLine());
    int L=Integer.parseInt(st.nextToken());
    int R=Integer.parseInt(st.nextToken());

    int count=0,sum=0,i=L,j=0;

    while(i>0){
    sum+=i%10;
    i=i/10;
    }
    if(L%sum==0)
        count++;
    for(i=L+1;i<=R;i++){
    if(i%10!=0){
    sum+=1;
    }
    else
    {
    j=i;
    while(j%10==0){
    sum-=9;
    j/=10;
    }
    sum+=1;
    }
    if(i%sum==0)
        count++;
    }
        out.println(count);
    }
    out.close();
} 
}

6 个答案:

答案 0 :(得分:2)

我们可以使用动态编程来解决这个问题。

观察:

  • 每个号码最多可包含10位数字,因此每个号码的最大数字总和将小于100。

因此,假设我们知道一个数字的总和,通过逐个处理数字,我们有四件事需要检查:

  • 当前数字是否大于下限。
  • 当前数字是否小于上限。
  • 当前数字的mod是多少。
  • 所有数字的当前总和是多少。

我们提出了这个函数int count(int digit, boolean larger, boolean smaller, int left, int mod),然后是dp状态:dp[digit][larger][smaller][left][mod]

对于每个测试用例,时间复杂度为number of possible sum^3 x number of digit = 100^3*10 = 10^7.

有50个测试用例 - &gt; 50 * 10 ^ 7 = 5 * 10 ^ 8次操作,仍在时限内。

Java代码:

static int[][][][][] dp;
static int[][][][][] check;
static int cur = 0;

public static void main(String[] args) throws FileNotFoundException {
    // PrintWriter out = new PrintWriter(new FileOutputStream(new File(
    // "output.txt")));
    PrintWriter out = new PrintWriter(System.out);
    Scanner in = new Scanner();


    int n = in.nextInt();
    dp = new int[11][2][2][82][82];
    check = new int[11][2][2][82][82];
    for (int i = 0; i < n; i++) {

        int l = in.nextInt();
        int r = in.nextInt();
        String L = "" + l;
        String R = "" + r;
        while (L.length() < R.length()) {
            L = "0" + L;
        }
        int result = 0;
        for (int j = 1; j <= 81; j++) {

            cur = cur + 1;
            result += count(0, 0, 0, j, 0, j, L, R);
        }
        out.println(result);
    }
    out.close();
}

public static int count(int index, int larger, int smaller, int left,
        int mod, int sum, String L, String R) {
    if (index == L.length()) {
        if (left == 0 && mod == 0) {
            return 1;
        }
        return 0;
    }
    if((L.length() - index) * 9 < left){
        return 0;
    }
    if (check[index][larger][smaller][left][mod] == cur) {
        return dp[index][larger][smaller][left][mod];
    }
    //System.out.println(cur);
    check[index][larger][smaller][left][mod] = cur;
    int x = L.charAt(index) - '0';
    int y = R.charAt(index) - '0';
    int result = 0;
    for (int i = 0; i < 10 && i <= left; i++) {

        if (x > i && larger == 0) {
            continue;
        }
        if (y < i && smaller == 0) {
            continue;
        }
        int nxtLarger = larger;
        int nxtSmaller = smaller;
        if (x < i) {
            nxtLarger = 1;
        }
        if (y > i) {
            nxtSmaller = 1;
        }
        int nxtMod = (mod * 10 + i) % sum;
        result += count(index + 1, nxtLarger, nxtSmaller, left - i, nxtMod,
                sum, L, R);
    }
    return dp[index][larger][smaller][left][mod] = result;
}

更新:我已提交并通过了此问题的所有测试用例,(第二个解决此问题的人)这是我的submission

的链接

答案 1 :(得分:2)

f (L, R) =“整数L≤x≤R,其中x可被其数字之和整除”。我们定义x = 0不计算在内。

g (M) =“整数1≤xf (L, R) = g (R + 1) - g (L)。

找到最大的k≥0,使得10 ^ k <= M.找到最大的a≥1使得a * 10 ^ k <= M.所有整数<1。 M作为数字之和最多为9k +(a-1)。

h (M, n) =“整数1≤xg (M)是h (M, n)的总和。

r (a, k, n) =“整数个数a * 10 ^k≤x<(a + 1)* 10 ^ k其中x可被n整除,并且数字之和为n”。可以通过以明显的方式添加h (M, n)的值来计算r (a, k, n);例如:

h (1,234,000,000, n) = r (0, 9, n) + r (10, 8, n) + r (11, 8, n) + r (120, 7, n) + r (121, 7, n) + r (122, 7, n) + r (1230, 6, n) + r (1231, 6, n) + r (1232, 6, n) + r (1233, 6, n).

f (k, n, d, m) =“整数0≤x<10 ^ k,其中数字之和为d,并且x%n = m”。我们可以使用此函数计算r (a, k, n):最后的k个数字必须具有n - digitsum(a)的数字和。如果整数可以被n整除,那么最后的k个数字必须具有( - a * 10 ^ k)%n的余数。所以r (a, k, n) = f (k, n, n - digitsum(a), - (a*10^k) % n)

如果k = 1,则

f (k, n, d, m)是微不足道的:仅对于数字d是等于d的数字之和,因此如果d%n = m则f (1, n, d, m)为1,否则为0。

要计算f (k+1, n, d, m),我们为0≤a≤9添加f (k, n, d-a, (m - a*10^k)%n)。显然,必须存储所有值f (k, n, d, m),以便不会一次又一次地重新计算它们。

就是这样。操作次数:如果R&lt; 10 ^ r,则数字最多为9r。我们计算值f (k, n, d, m)为1≤k≤r,1≤n≤9r,0≤d≤9r,0≤m≤1。 ñ。对于每个我们添加10个不同的数字,所以我们增加了不到10,000 r ^ 4。所以最多10 ^ 19的数字都没问题。

答案 2 :(得分:1)

以下方法每个案例大约需要10 ^ 7次操作。

将数字拆分为前缀(n / 10000)和后缀(n%10000)。选择数字总和后,只需要每个前缀和后缀中的一小部分数据来确定数字总和是否除以数字。 (这与gnasher729所说的一些事情有关,但我的运行时间差异很大。)

For each possible digit sum d from 1 to 81,
    Map prefix p to a pair (p*10000 % d, digit sum(p)). 
    Tally the counts in a matrix M.
    Map each possible suffix s to a pair (s % d, digit sum(s)). 
    Tally the counts in a matrix N.
    For every (a,b), 
        total += M[a,b] *N[-a%d,d-b]

大约有81 *(10 ^ 5 + 10 ^ 4)步。

部分允许前缀的边缘情况(L / 10000,R / 10000和100000)可以在大约20000步骤中强制执行一次。

答案 3 :(得分:0)

有趣的问题。直接的解决方案是迭代从L到R的数字,计算每个数字的总和,并检查每个数字是否可以被数字之和整除。

显然可以更快地计算数字之和。数字xxx0,xxx1,xxx2,...,xxx9具有数字和n,n + 1,n + 2,...,n + 9。因此,对于十个连续的数字,几乎不需要计算数字总和,只需要一个模数运算来检查可分性。

模数检查可以更快。编译器使用聪明的技巧来除以常数,用移位和乘法替换慢速除法。您可以搜索这是如何完成的,并且因为只有81个可能的除数,所以在运行时执行编译器对常量的操作。这应该会使每个数字的时间减少到几纳秒。

做得更好:我会循环检查数字和1,数字和2等数字。例如,假设我用数字和17检查数字。这些数字必须有数字和为17,也是17的倍数。我取0000到9999的数字,每个我计算数字之和,模数为17,然后将它们分成37 x 17组,其中所有数字都在该组具有相同的数字和,并且相同的值以模数17为单位并计算每组中的元素。

然后检查从0到9999的数字:我选择数字和为17的集合,模数17的值为0并取该集合的元素计数。要检查10,000到19,999之间的数字:我选择数字和为16的集合,模数17的值为13(因为10013可被17整除),依此类推。

这就是这个想法。我认为有点聪明,可以扩展到一个方法,采用O(log ^ 4 R)步骤来处理从L到R的所有数字。

答案 4 :(得分:0)

在下面的C代码中,我专注于核心部分,即找到DSD计数。该代码无疑是丑陋的,但这是你在匆忙编码时所得到的。

基本观察是通过单独跟踪数字的数字可以简化数字和,从而将数字和确定减少到每个步骤中的简单增量/减量。有可能是加速模数计算的聪明方法,我无法想出任何关于双重计算的内容。

在我的机器(Xeon E3 1270 v2,3.5 GHz)上,下面的代码在3.54秒内找到[1,1e9]中的DSD数。我在MSVC 2010上编译了优化级别-O2。虽然您在更新问题时声明了1秒的时间限制,但不清楚这种极端情况是由您提到的网站上的框架行使的。无论如何,这将为比较其他提出的解决方案提供合理的基线。

#include <stdio.h>
#include <stdlib.h>

/* sum digits in decimal representation of x */
int digitsum (int x)
{
  int sum  = 0;
  while (x) {
    sum += x % 10;
    x = x / 10;
  }
  return sum;
}

/* split integer into individual decimal digits. p[0]=ones, p[1]=tens, ... */
void split (int a, int *p) 
{
  int i = 0;
  while (a) {
    p[i] = a % 10;
    a = a / 10;
    i++;
  }
}

/* return number of DSDs in [first,last] inclusive. first, last in [1,1e9] */
int count_dsd (int first, int last)
{
  int num, ds, count = 0, p[10] = {0};
  num = first;
  split (num, p);
  ds = digitsum (num);
  while (p[9] < 10) {
    while (p[8] < 10) {
      while (p[7] < 10) {
        while (p[6] < 10) {
          while (p[5] < 10) {
            while (p[4] < 10) {
              while (p[3] < 10) {
                while (p[2] < 10) {
                  while (p[1] < 10) {
                    while (p[0] < 10) {
                      count += ((num % ds) == 0);
                      if (num == last) {
                        return count;
                      }
                      num++;
                      p[0]++;
                      ds++;
                    }
                    p[0] = 0;
                    p[1]++;
                    ds -= 9;
                  }
                  p[1] = 0;
                  p[2]++;
                  ds -= 9;
                }
                p[2] = 0;
                p[3]++;
                ds -= 9;
              }
              p[3] = 0;
              p[4]++;
              ds -= 9;
            }
            p[4] = 0;
            p[5]++;
            ds -= 9;
          }
          p[5] = 0;
          p[6]++;
          ds -= 9;
        }
        p[6] = 0;
        p[7]++;
        ds -= 9;
      }
      p[7] = 0;
      p[8]++;
      ds -= 9;
    }
    p[8] = 0;
    p[9]++;
    ds -= 9;
  }  
  return count;
}

int main (void)
{
  int i, first, last, *count, testcases;
  scanf ("%d", &testcases);
  count = malloc (testcases * sizeof(count[0]));
  if (!count) return EXIT_FAILURE;
  for (i = 0; i < testcases; i++) {
    scanf ("%d %d", &first, &last);
    count[i] = count_dsd (first, last);
  }
  for (i = 0; i < testcases; i++) {
    printf ("%d\n", count[i]);
  }
  free (count);
  return EXIT_SUCCESS;
}

我将问题中陈述的示例输入复制到文本文件testdata中,当我像这样调用可执行文件时:

dsd < testdata

输出符合要求:

4 10 9 33

答案 5 :(得分:0)

Java 中的解决方案

<块引用>

实现一个程序来判断一个数是否能被其数字之和整除。 显示适当的消息。


class DivisibleBySum
{
     public static void main(String[] args) 
     {
        // Implement your code here 
        int num = 123;
        int number = num;
        int sum=0;
        for(;num>0;num /=10)
        {
            int rem = num % 10;
            sum += rem;
        }
        if(number %sum ==0)
           System.out.println(number+" is divisible by sum of its digits");
        else
           System.out.println(number+" is not divisible by sum of its digits");
    }
}