我正在尝试解决Project Euler problem 240:



我想出了代码来解决这个问题。但是计算确实需要很多时间。我知道这种方法很糟糕。有人可以建议我如何解决此代码以使其性能更好吗?

import itertools
def check(a,b):   # check all the elements in a list a, are lesser than or equal to value b
    chk=0
    for x in a:
        if x<=b:
            chk=1
    return chk

lst=[]
count=0
for x in itertools.product(range(1,13),repeat=20):
    a=sorted([x[y] for y in range(20)])
    if sum(a[-10:])==70 and check(a[:10],min(a[-10:])):
        count+=1

以下代码用于problem的描述中定义的问题。它完美地工作,并给出了确切的解决方案。
import itertools
def check(a,b):
     chk=1
     for x in a:
         if x>b:
             chk=0
             break
     return chk


count=0
for x in itertools.product(range(1,7),repeat=5):
    a=sorted([x[y] for y in range(5)])
    if sum(a[-3:])==15 and check(a[:2],min(a[-3:])):
        count+=1

最佳答案

迭代所有可能性都不是一件好事,因为有1220 = 3833759992447475122176的方法来滚动20个十二面骰子,并且以每秒一百万的速度滚动,将需要数百万年的时间才能完成。

解决此类问题的方法是使用dynamic programming。寻找某种方法将您的问题分解为几个较小的问题的总和,并为这些子问题建立答案表,直到您可以计算出所需的结果为止。

例如,令T(n,d,k,t)为掷出n个d边骰子的方式数,以使它们的前k个总和为t。我们如何将其分解为子问题?好吧,我们可以考虑准确地掷出d的骰子数量i。有nCi种方法可以选择这些骰子,还有T(n−i,d-1,...)种方法可以选择剩余的n−i个骰子,这些骰子最多只能滚动d − 1(对于某些合适的参数选择对于我遗失的k和t。)

取这些乘积,然后将其求和为i的所有合适值,就可以了。 (嗯,还没做完:您必须指定基本情况,但这应该很容易。)

您需要计算的子问题数最多为(n +1)(d +1)(k +1)(t +1),在Project Euler情况下为(n = 20,d = 12 ,k = 10,t = 70)最多为213213。(实际上,它远不止于此),因为树的许多分支很快就可以到达基本情况:在我的实现中,结果发现只有791个子问题的答案足以计算答案。)

要编写动态程序,通常最简单的方法是递归地表达它,并使用memoization避免重新计算子问题的答案。在Python中,您可以使用 @functools.lru_cache decorator

因此,程序的框架可能看起来像这样。我已用???替换了关键的细节,以免使您失去为自己解决它的乐趣。在尝试更大的案例之前,请使用一些小的示例(例如“两个6面骰子,其中前1个总数为6”)来检查您的逻辑是否正确。

def combinations(n, k):
    """Return C(n, k), the number of combinations of k out of n."""
    c = 1
    k = min(k, n - k)
    for i in range(1, k + 1):
        c *= (n - k + i)
        c //= i
    return c

@lru_cache(maxsize=None)
def T(n, d, k, t):
    """Return the number of ways n distinguishable d-sided dice can be
    rolled so that the top k dice sum to t.

    """
    # Base cases
    if ???: return 1
    if ???: return 0

    # Divide and conquer. Let N be the maximum number of dice that
    # can roll exactly d.
    N = ???
    return sum(combinations(n, i)
               * T(n - i, d - 1, ???)
               for i in range(N + 1))

通过为所有???选择适当的选项,可以在几毫秒内回答Project Euler问题:
>>> from timeit import timeit
>>> timeit(lambda:T(20, 12, 10, 70), number=1)
0.008017531014047563
>>> T.cache_info()
CacheInfo(hits=1844, misses=791, maxsize=None, currsize=791)

10-06 00:20