编程竞赛的一个问题......数字和

时间:2010-09-01 01:25:33

标签: algorithm

我需要帮助解决problem N from this earlier competition

问题N:数字总和

  

给出3个正整数A,B和C,   找到多少正整数   当表达时,大于或等于A.   基数B,具有与C相加的数字。

     

输入将包含一系列   每行包含三个整数,   A,B和C,2≤B≤100,1≤A,C≤   10亿。数字A,B和C.   在基数10中给出并分开   由一个或多个空白。输入是   由包含三行的行终止   零。

     

输出将是数字的数量,   对于每个输入行(必须给出它)   在基地10)。

示例输入

100 10 9
100 10 1
750000 2 2
1000000000 10 40
100000000 100 200
0 0 0

示例输出

10
3
189
45433800
666303

相关规则:

  1. 阅读键盘上的所有输入,即使用stdinSystem.incin或同等信息。输入将从文件重定向,以形成提交的输入。

  2. 将所有输出写入屏幕,即使用stdoutSystem.outcout或同等效果。不要写信给stderr。请勿使用或甚至包含任何允许直接操作屏幕的模块,例如conioCrt或类似内容。程序的输出被重定向到一个文件以供以后检查。使用直接I / O意味着不会重定向此类输出,因此无法检查。这可能意味着正确的程序被拒绝了!

  3. 除非另有说明,否则输入中的所有整数都将适合标准的32位计算机字。一条线上的相邻整数将由一个或多个空格分隔。

  4. 当然,公平地说,在尝试解决这个问题之前我应该​​学习更多,但如果有人在这里告诉我它是如何完成的,我真的很感激。

    先谢谢,约翰。

4 个答案:

答案 0 :(得分:6)

其他人指出了简单的解决方案:迭代从1到A的所有数字。但实际上,这个问题可以在几乎恒定的时间内解决:O(length of A),即O(log(A))

  1. 提供的代码适用于基础10.根据任意基础调整它是微不足道的。
  2. 要达到上述时间估算值,您需要将memorization添加到递归中。如果您对该部分有疑问,请与我联系。
  3. 现在,递归函数本身。用Java编写,但一切都应该在C#/ C ++中运行而不做任何更改。它很大,但主要是因为我试图澄清算法的评论。

    // returns amount of numbers strictly less than 'num' with sum of digits 'sum'
    // pay attention to word 'strictly'
    int count(int num, int sum) {
        // no numbers with negative sum of digits
        if (sum < 0) {
            return 0;
        }
    
        int result = 0;
    
        // imagine, 'num' == 1234
        // let's check numbers 1233, 1232, 1231, 1230 manually
        while (num % 10 > 0) {
            --num;
    
            // check if current number is good
            if (sumOfDigits(num) == sum) {
                // one more result
                ++result;
            }
        }
    
        if (num == 0) {
            // zero reached, no more numbers to check
            return result;
        }
    
        num /= 10;
    
        // Using example above (1234), now we're left with numbers
        // strictly less than 1230 to check (1..1229)
        // It means, any number less than 123 with arbitrary digit appended to the right
        // E.g., if this digit in the right (last digit) is 3,
        // then sum of the other digits must be "sum - 3"
        // and we need to add to result 'count(123, sum - 3)'
    
        // let's iterate over all possible values of last digit
        for (int digit = 0; digit < 10; ++digit) {
            result += count(num, sum - digit);
        }
    
        return result;
    }
    

    辅助功能

    // returns sum of digits, plain and simple
    int sumOfDigits(int x) {
        int result = 0;
        while (x > 0) {
            result += x % 10;
            x /= 10;
        }
        return result;
    }
    

    现在,让我们写一个小测试人员

        int A = 12345;
        int C = 13;
    
        // recursive solution
        System.out.println(count(A + 1, C));
    
        // brute-force solution 
        int total = 0;
        for (int i = 1; i <= A; ++i) {
            if (sumOfDigits(i) == C) {
                ++total;
            }
        }
        System.out.println(total);
    

    您可以编写更全面的测试人员检查A的所有值,但整体解决方案似乎是正确的。 (我尝试了几个随机的A和C。)

    不要忘记,你不能在没有记忆的情况下测试A == 1000000000的解决方案:它会运行太长时间。但是通过记忆,您甚至可以对A == 10^1000进行测试。

    修改
    只是为了证明一个概念,穷人的记忆。 (在Java中,在其他语言中,哈希表的声明方式不同)但是如果你想学习一些东西,那么最好自己尝试一下。

    // hold values here
    private Map<String, Integer> mem;
    
    int count(int num, int sum) {
        // no numbers with negative sum of digits
        if (sum < 0) {
            return 0;
        }
    
        String key = num + " " + sum;
        if (mem.containsKey(key)) {
            return mem.get(key);
        }
    
        // ... 
        // continue as above...
        // ...
    
        mem.put(key, result);
        return result;
    }
    

答案 1 :(得分:2)

这是Rybak发布的相同的memoized递归解决方案,但是我的拙见认为实现更简单:

HashMap<String, Integer> cache = new HashMap<String, Integer>();

int count(int bound, int base, int sum) {
    // No negative digit sums.
    if (sum < 0)
        return 0;

    // Handle one digit case.
    if (bound < base)
        return (sum <= bound) ? 1 : 0;

    String key = bound + " " + sum;
    if (cache.containsKey(key))
        return cache.get(key);

    int count = 0;
    for (int digit = 0; digit < base; digit++)
        count += count((bound - digit) / base, base, sum - digit);

    cache.put(key, count);
    return count;
}

答案 2 :(得分:1)

这不是完整的解决方案(没有输入解析)。要获得基数B中的数字,重复取模B,然后除以B直到结果为0.这有效地计算了右边的基数B,然后将数字右移。

int A,B,C; // from input
for (int x=1; x<A; x++)
{
    int sumDigits = 0;
    int v = x;
    while (v!=0) {
       sumDigits += (v % B);
       v /= B;
    }
    if (sumDigits==C)
       cout << x;
}

这是一种蛮力方法。有可能通过确定哪些基本B组数字加起来C,在所有小于A的排列中排列这些数字,然后从那里向后工作以创建原始数字来更快地计算这一点。

答案 3 :(得分:0)

荫。

试试这个:

int number, digitSum, resultCounter = 0;

for(int i=1; i<=A, i++)
{
   number = i; //to avoid screwing up our counter
   digitSum = 0;
   while(number > 1)
   {
      //this is the next "digit" of the number as it would be in base B; 
      //works with any base including 10.
      digitSum += (number % B);
      //remove this digit from the number, square the base, rinse, repeat 
      number /= B;
   }
   digitSum += number;

   //Does the sum match?       
   if(digitSum == C)
      resultCounter++;
}

这是一行的基本算法。现在,将此包装在您收到的每个输入行的另一个For循环中,前面是输入收集阶段本身。这个过程可以简化,但我不想编写你的整个答案,看看我的算法是否有效,这看起来是正确的,而更简单的技巧更难通过检查。

这种方式的工作方式是以模数除以基数。简单的例子,基数10中的1234:

1234 % 10 = 4
1234 / 10 = 123 //integer division truncates any fraction
123 % 10 = 3 //sum is 7
123 / 10 = 12
12 % 10 = 2 //sum is 9
12 / 10 = 1 //end condition, add this and the sum is 10

通过检查得出的一个更难的例子是基数12中的相同数字:

1234 % 12 = 10 //you can call it "A" like in hex, but we need a sum anyway
1234 / 12 = 102
102 % 12 = 6 // sum 16
102/12 = 8
8 % 12 = 8 //sum 24
8 / 12 = 0 //end condition, sum still 24.

因此基地12中的1234将被写为86A。检查数学:

8*12^2 + 6*12 + 10 = 1152 + 72 + 10 = 1234

尽情享受包围其余代码的乐趣。