【问题标题】:Wrong answer in SPOJ `CUBERT` [closed]SPOJ`CUBERT`中的错误答案[关闭]
【发布时间】:2016-02-07 14:15:45
【问题描述】:

我在 SPOJ 上对this problem 的解决方案收到了错误答案

问题要求计算整数的立方根(最长可达 150 位),并输出截断至小数点后 10 位的答案。
它还要求将答案模 10 中所有数字的总和计算为“校验和”值。

这是确切的问题陈述:

你的任务是计算给定正整数的立方根。 我们不记得为什么我们需要这个,但它有一些东西 常见于公主、年轻农民、接吻和半个王国 (一个巨大的,我们可以向你保证)。

编写一个程序来解决这个关键任务。

输入

输入以包含单个整数 t

接下来的几行包含最多 150 个十进制的大正整数 位数。每个数字都在输入文件的单独行中。这 输入文件可能包含空行。数字可以在前面或 后跟空格,但每行不得超过 255 个字符。

输出

对于输入文件中的每个数字,您的程序应该输出一行 由由单个空格分隔的两个值组成。第二个值 是给定数字的立方根,截断(不四舍五入!)后 小数点后 10 位。第一个值是所有打印的校验和 立方根的位数,计算为打印位数的总和 模 10。

示例

输入
5
1

8

1000

2 33076161

输出
1 1.0000000000
2 2.0000000000
1 10.0000000000
0 1.2599210498
6 321.0000000000

这是我的解决方案:

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()

它非常适合示例案例:Live demo

谁能告诉这个解决方案有什么问题?

【问题讨论】:

  • 请说明被否决的原因。
  • It fails 在长数字上,例如 (10^50-1)^3
  • 问题是普通浮点数不能准确地表示如此大数的立方根。即使您尝试使用较小的数字,例如1233412412430519230351035712112421123121111,您也只会看到小数点后的 6 位数字,其他数字全为零。
  • 即使在较小的数字上,您的截断过程也不起作用:考虑 foo(1026) == (9, '10.0859262204'),但第十位小数应该是 3。
  • EgorSkriptunoff 和 Rishav 感谢您指出这一点。

标签: python python-3.x math floating-point precision


【解决方案1】:

您的解决方案有两个问题,都与浮点运算的使用有关。第一个问题是 Python floats 仅携带大约 16 位有效十进制数字的精度,因此只要您的答案需要超过 16 位有效数字左右(因此点前超过 6 位,点后超过 10 位),您几乎没有希望获得正确的尾随数字。第二个问题更微妙,甚至影响n 的小值。那是因为double rounding,您将舍入到 11 位十进制数字然后删除最后一位数字的方法存在潜在错误。以n = 33 为例。 n 的立方根,小数点后 20 位左右,为:

3.20753432999582648755...

当它在该点之后四舍五入到 11 位时,您最终得到

3.20753433000

现在去掉最后一个数字会得到3.2075343300,这不是你想要的。问题是,小数点后 11 位可能最终会影响第 11 位数字左侧的数字。

那么你能做些什么来解决这个问题呢?好吧,您可以完全避免浮点并将其简化为纯整数问题。我们需要某个整数 n 的立方根到小数点后 10 位(将最后一位向下舍入)。这相当于将10**30 * n 的立方根计算为最接近的整数,再次向下舍入,然后将结果除以10**10。所以这里的基本任务是计算任何给定整数n 的立方根的底。我无法找到任何有关计算整数立方根的现有 Stack Overflow 答案(在 Python 中仍然更少),所以我认为值得详细展示如何做到这一点。

计算整数的立方根非常容易(借助一点点数学知识)。有多种可能的方法,但一种既有效又易于实现的方法是使用Newton-Raphson 方法的纯整数版本。在实数上,牛顿求解方程x**3 = n 的方法采用xn 的立方根的近似值,并迭代以返回改进的近似值。所需的迭代是:

x_next = (2*x + n/x**2)/3

在实际情况下,您会重复迭代,直到达到某个所需的容差。事实证明,在整数上,基本上相同的迭代工作,并且在正确的退出条件下,它会给我们完全正确的答案(不需要公差)。整数情况下的迭代为:

a_next = (2*a + n//a**2)//3

(注意使用地板除法运算符// 代替上面通常的真正除法运算符/。)在数学上,a_next 恰好是(2*a + n/a**2)/3 的地板。

以下是基于此迭代的一些代码:

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

还有一些例子使用:

>>> icbrt_v1(100)
4
>>> icbrt_v1(1000000000)
1000
>>> large_int = 31415926535897932384626433
>>> icbrt_v1(large_int**3)
31415926535897932384626433
>>> icbrt_v1(large_int**3-1)
31415926535897932384626432

icbrt_v1 中存在一些烦恼和效率低下的问题,我们将很快解决。但首先,简要解释一下上述代码的工作原理。请注意,我们从假设大于或等于立方根的底的初始猜测开始。我们将证明这个属性是一个循环不变量:每次我们到达 while 循环的顶部,a 至少是floor(cbrt(n))。此外,每次迭代产生的值a 严格小于旧值,因此我们的迭代保证最终收敛到floor(cbrt(n))。为了证明这些事实,请注意,当我们进入while 循环时,有两种可能性:

案例 1。a 严格大于 n 的立方根。然后a &gt; n//a**2,代码继续下一次迭代。写a_next = (2*a + n//a**2)//3,我们就有了:

  • a_next &gt;= floor(cbrt(n))。这是因为(2*a + n/a**2)/3 至少是n 的立方根,而AM-GM inequality 又适用于aaan/a**2:这三个量的几何平均值正好是n 的立方根,所以算术平均值必须至少n 的立方根。所以我们的循环不变量被保留用于下一次迭代。

  • a_next &lt; a:因为我们假设a 大于立方根n/a**2 &lt; a,因此(2a + n/a**2) / 3 小于a ,因此floor((2a + n/a**2) / 3) &lt; a。这保证了我们在每次迭代中都能在解决方案上取得进展。

案例 2。a 小于或等于 n 的立方根。然后是a &lt;= floor(cbrt(n)),但是从上面建立的循环不变量我们也知道a &gt;= floor(cbrt(n))。所以我们完成了:a 是我们所追求的值。而此时循环退出,因为a &lt;= n // a**2

上面的代码有几个问题。首先,从n 的初始猜测开始是低效的:代码将花费其最初的几次迭代(大致)将a 的当前值除以3,直到它进入解决方案的附近。初始猜测的更好选择(并且可以在 Python 中轻松计算)是使用超过 n 的立方根的 2 的第一次幂。

initial_guess = 1 << -(-n.bit_length() // 3)

如果n 足够小以避免溢出,更好的是使用浮点运算来提供初始猜测,例如:

initial_guess = int(round(n ** (1/3.)))

但这给我们带来了第二个问题:我们算法的正确性要求初始猜测不小于实际整数立方根,并且随着n 变大,我们不能保证对于基于浮点数的上面的initial_guess(尽管对于足够小的n,我们可以)。幸运的是,有一个非常简单的解决方法:对于 any 正整数 a,如果我们执行一次迭代,我们总是会得到一个至少为 floor(cbrt(a)) 的值(使用相同的 AM-GM 参数我们上面使用的)。因此,我们所要做的就是在开始测试收敛之前至少执行一次迭代。

考虑到这一点,下面是上述代码的更高效版本:

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

有了icbrt,可以轻松地将所有内容放在一起计算立方根,精确到小数点后十位。在这里,为了简单起见,我将结果输出为字符串,但您也可以轻松构造一个 Decimal 实例。

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)

示例输出:

>>> 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'

【讨论】:

  • 哇,漂亮的答案让我完全无法理解!
  • 太棒了,最好的是通过一些小的改动你可以扩展它来计算第n个根
【解决方案2】:

您可以尝试使用具有足够大精度值的decimal 模块。

编辑:感谢@DSM,我意识到decimal 模块不会产生非常精确的立方根。我建议您检查所有数字是否都是 9,如果是这种情况,请将其四舍五入为整数。

另外,我现在也用 Decimal 执行 1/3 除法,因为将 1/3 的结果传递给 Decimal 构造函数会导致精度降低。

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]))

输出:

107243119477324.80328931501744819161741924145124146
107243119477324.8032893150

cbrt(10) 的输出是:

9.9999999999999999999999999999999999999999999999998

【讨论】:

  • 咦,为什么投反对票?
  • NMDV,但是(奇怪的名字:-) isqrt 函数在这里不能很好地工作。考虑isqrt(1000),应该是10。
  • @DSM 好点,这让我在代码中发现了一个错误 :)
  • 请注意,您需要根据输入的大小动态更改精度:对于 150 位输入,您需要 60 位有效数字的输出(点前 50,点后 10) ,例如,在这种情况下,精度至少应为60;除此之外,您还需要一些保护数字。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-11-21
  • 2015-03-25
相关资源
最近更新 更多