我需要帮助解决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
相关规则:
阅读键盘上的所有输入,即使用stdin
,System.in
,cin
或同等信息。输入将从文件重定向,以形成提交的输入。
将所有输出写入屏幕,即使用stdout
,System.out
,cout
或同等效果。不要写信给stderr
。请勿使用或甚至包含任何允许直接操作屏幕的模块,例如conio
,Crt
或类似内容。程序的输出被重定向到一个文件以供以后检查。使用直接I / O意味着不会重定向此类输出,因此无法检查。这可能意味着正确的程序被拒绝了!
除非另有说明,否则输入中的所有整数都将适合标准的32位计算机字。一条线上的相邻整数将由一个或多个空格分隔。
当然,公平地说,在尝试解决这个问题之前我应该学习更多,但如果有人在这里告诉我它是如何完成的,我真的很感激。
先谢谢,约翰。
答案 0 :(得分:6)
其他人指出了简单的解决方案:迭代从1到A
的所有数字。但实际上,这个问题可以在几乎恒定的时间内解决:O(length of A)
,即O(log(A))
。
现在,递归函数本身。用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
尽情享受包围其余代码的乐趣。