Wrong answer in SPOJ `CUBERT`
对于我在SPOJ上解决这个问题的方法,我得到了一个错误的答案。
这个问题要求计算一个整数的立方根(最长可达150位),并输出截断到10位小数的答案。它还要求计算作为"校验和"值的应答模块10中所有数字的总和。
以下是确切的问题陈述:
Your task is to calculate the cube root of a given positive integer.
We can not remember why exactly we need this, but it has something in
common with a princess, a young peasant, kissing and half of a kingdom
(a huge one, we can assure you).Write a program to solve this crucial task.
Input
The input starts with a line containing a single integer t <= 20, the number of test cases. t test cases follow.
The next lines consist of large positive integers of up to 150 decimal
digits. Each number is on its own separate line of the input file. The
input file may contain empty lines. Numbers can be preceded or
followed by whitespaces but no line exceeds 255 characters.Output
For each number in the input file your program should output a line
consisting of two values separated by single space. The second value
is the cube root of the given number, truncated (not rounded!) after
the 10th decimal place. First value is a checksum of all printed
digits of the cube root, calculated as the sum of the printed digits
modulo 10.Example
Input:
5
18
1000
2 33076161
Output:
1 1.0000000000
2 2.0000000000
1 10.0000000000
0 1.2599210498
6 321.0000000000
号
这是我的解决方案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | from math import pow def foo(num): num_cube_root = pow(num, 1.0 / 3) # First round upto 11 decimal places num_cube_root ="%.11f" % (num_cube_root) # Then remove the last decimal digit # to achieve a truncation of 10 decimal places num_cube_root = str(num_cube_root)[0:-1] num_cube_root_sum = 0 for digit in num_cube_root: if digit != '.': num_cube_root_sum += int(digit) num_cube_root_sum %= 10 return (num_cube_root_sum, num_cube_root) def main(): # Number of test cases t = int(input()) while t: t -= 1 num = input().strip() # If line empty, ignore if not num: t += 1 continue num = int(num) ans = foo(num) print(str(ans[0]) +"" + ans[1]) if __name__ == '__main__': main() |
它非常适合示例案例:实时演示。
有人能说出这个解决方案的问题是什么吗?
您的解决方案有两个问题,都与浮点运算的使用有关。第一个问题是,python
1 | 3.20753432999582648755... |
当这一点被四舍五入到11位时,你最终会得到好的。
1 | 3.20753433000 |
号
现在去掉最后一个数字就得到了
那么你能做些什么来解决这个问题呢?好吧,您可以完全避免浮点运算,并将其简化为纯整数问题。我们需要整数
计算整数的立方根变得相当容易(借助于一点点数学知识)。有各种可能的方法,但一种既有效又易于实现的方法是使用纯整数版本的牛顿-拉斐逊方法。在实数上,牛顿求解方程
1 | x_next = (2*x + n/x**2)/3 |
在实际情况下,您将重复迭代,直到达到所需的公差。事实证明,在整数上,基本上相同的迭代是可行的,并且在正确的退出条件下,它将给出正确的答案(不需要公差)。整数情况下的迭代为:好的。
1 | a_next = (2*a + n//a**2)//3 |
。
(注意使用楼层划分操作符
下面是一些基于此迭代的代码:好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | def icbrt_v1(n, initial_guess=None): """ Given a positive integer n, find the floor of the cube root of n. Args: n : positive integer initial_guess : positive integer, optional. If given, this is an initial guess for the floor of the cube root. It must be greater than or equal to floor(cube_root(n)). Returns: The floor of the cube root of n, as an integer. """ a = initial_guess if initial_guess is not None else n while True: d = n//a**2 if a <= d: return a a = (2*a + d)//3 |
一些示例使用:好的。
1 2 3 4 5 6 7 8 9 | >>> icbrt_v1(100) 4 >>> icbrt_v1(1000000000) 1000 >>> large_int = 31415926535897932384626433 >>> icbrt_v1(large_int**3) 31415926535897932384626433 >>> icbrt_v1(large_int**3-1) 31415926535897932384626432 |
案例1。
a_next >= floor(cbrt(n)) 。这是因为(2*a + n/a**2)/3 至少是n 的立方根,而这个立方根又是a 、a 和n/a**2 的AM-GM不等式的结果:这三个量的几何平均数正好是n 的立方根,所以算术平均数必须至少是EDOCX1的立方根。1〔7〕。所以我们的循环不变量被保留下来,以便下一次迭代。好的。a_next < a :由于我们假设a 大于立方根,n/a**2 < a ,因此(2a + n/a**2) / 3 小于a ,因此floor((2a + n/a**2) / 3) < a 。这保证了我们在每次迭代中都朝着解决方案前进。好的。
案例2。
上面的代码有几个问题。首先,从最初对
1 | initial_guess = 1 << -(-n.bit_length() // 3) |
号
更妙的是,如果
1 | initial_guess = int(round(n ** (1/3.))) |
但这又带来了我们的第二个问题:算法的正确性要求初始猜测不小于实际的整数立方根,并且随着
有鉴于此,下面是上述代码的更有效版本:好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | def icbrt(n): """ Given a positive integer n, find the floor of the cube root of n. Args: n : positive integer Returns: The floor of the cube root of n, as an integer. """ if n.bit_length() < 1024: # float(n) safe from overflow a = int(round(n**(1/3.))) a = (2*a + n//a**2)//3 # Ensure a >= floor(cbrt(n)). else: a = 1 << -(-n.bit_length()//3) while True: d = n//a**2 if a <= d: return a a = (2*a + d)//3 |
号
有了
1 2 3 4 5 6 7 8 9 | def cbrt_to_ten_places(n): """ Compute the cube root of `n`, truncated to ten decimal places. Returns the answer as a string. """ a = icbrt(n * 10**30) q, r = divmod(a, 10**10) return"{}.{:010d}".format(q, r) |
示例输出:好的。
1 2 3 4 5 6 7 8 | >>> cbrt_to_ten_places(2) '1.2599210498' >>> cbrt_to_ten_places(8) '2.0000000000' >>> cbrt_to_ten_places(31415926535897932384626433) '315536756.9301821867' >>> cbrt_to_ten_places(31415926535897932384626433**3) '31415926535897932384626433.0000000000' |
。好啊。
您可以尝试使用具有足够大精度值的
编辑:多亏了@dsm,我意识到
另外,我现在也用小数进行1/3除法,因为将1/3的结果传递给十进制构造函数会降低精度。
1 2 3 4 5 6 7 8 9 10 11 12 13 | import decimal def cbrt(n): nd = decimal.Decimal(n) with decimal.localcontext() as ctx: ctx.prec = 50 i = nd ** (decimal.Decimal(1) / decimal.Decimal(3)) return i ret = str(cbrt(1233412412430519230351035712112421123121111)) print(ret) left, right = ret.split('.') print(left + '.' + ''.join(right[:10])) |
输出:
1 2 | 107243119477324.80328931501744819161741924145124146 107243119477324.8032893150 |
号
1 | 9.9999999999999999999999999999999999999999999999998 |