【问题标题】:A problem from a programming competition... Digit Sums编程竞赛中的一个问题... 数字总和
【发布时间】:2011-04-06 13:53:26
【问题描述】:

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

问题 N:数字和

给定 3 个正整数 A、B 和 C, 找出少了多少正整数 大于或等于 A,当表示为 以 B 为底,数字之和为 C。

输入将由一系列 行,每行包含三个整数, A, B 和 C, 2 ≤ B ≤ 100, 1 ≤ A, C ≤ 1,000,000,000。数字 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 位计算机字。一行中相邻的整数将由一个或多个空格分隔。

当然,公平地说,在尝试解决此问题之前我应该​​了解更多信息,但如果有人告诉我它是如何完成的,我将不胜感激。

提前致谢,约翰。

【问题讨论】:

  • 我是否真的必须下载整个 PDF 文件才能阅读您可能在此处剪切和粘贴的内容——只是为了看看我是否想帮助您?
  • 给定 3 个正整数 A、B 和 C,找出有多少个小于或等于 A 的正整数,当以 B 为基数表示时,有数字之和为 C。输入将由一系列行,每行包含三个整数,A、B 和 C,2 ≤ B ≤ 100,1 ≤ AC ≤ 1,000,000,000。数字 A、B 和 C 以 10 为底数,并由一个或多个空格分隔。输入由包含三个零的行终止。输出将是每个输入行的数字数量(必须以 10 为基数)。
  • 规则说输入中的所有整数都适合 32 位,所以上限实际上是 4,294,967,295(假设它是无符号的,因为 A 不能是负值)。
  • 问题中说明了A的上限,与C的上限相同。

标签: algorithm


【解决方案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 的所有排列中,然后从该数字逆推以创建原始数字,从而更快地计算出这个数字。

【讨论】:

  • 问题是这个条件:1 ≤ A,C ≤ 1,000,000,000。经历 1000000000 让我认为有更好的方法,因为在这种情况下,蛮力将需要相当长的时间。不过谢谢。您的其他建议似乎很有帮助。
  • 我同意!我不确定是不是让你感到困惑的是基本转换。但是在编写并实现A和C的巨大上限之后,反向操作会更有效率。 (尽管现代 CPU 会很快地完成 10 亿个循环,如果问题被并行解决,甚至更快。)
【解决方案2】:

嗯。

试试这个:

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

享受围绕此包装其余代码的乐趣。

【讨论】:

    【解决方案3】:

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

    1. 提供的代码适用于基数 10。将其调整为任意基数很简单。
    2. 要达到上述估计时间,您需要在递归中添加memorization。如果您对此部分有任何疑问,请告诉我。

    现在,递归函数本身。用 Java 编写,但一切都应该在 C#/C++ 中运行,无需任何更改。它很大,但主要是因为我试图阐明算法的 cmets。

    // 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-10)并从总和中减去它们。再次感谢。
    • 只是一个关于记忆的小问题,这和动态编程是一回事吗? DP 和记忆递归是相似的(通常相同的算法可以用任何这些技术)。
    • 我还编辑了帖子以澄清数字。这个想法是:如果最后一个数字是 3,那么其余数字的总和必须是“sum - 3”。
    【解决方案4】:

    在我看来,这是 Rybak 发布的相同的记忆递归解决方案,但实现更简单:

    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;
    }
    

    【讨论】:

      猜你喜欢
      • 2011-06-06
      • 2013-12-26
      • 1970-01-01
      • 1970-01-01
      • 2011-08-21
      • 2010-09-14
      • 1970-01-01
      • 2021-09-15
      • 1970-01-01
      相关资源
      最近更新 更多