【问题标题】:Speed up search for the smallest x such that f(x) = target加快搜索最小的 x 使得 f(x) = target
【发布时间】:2020-11-13 19:22:05
【问题描述】:

问题

给定n,找到最小的正x,使得f(x) = n。

f(x) 是x 的数字的阶乘的数字总和。

例如,

    f(15) 
    = digit_sum(1!) + digit_sum(5!)
    = digit_sum(1) + digit_sum(120)
    = (1) + (1 + 2 + 0)
    = 4

呼吸优先搜索可以找到答案。有更快的方法吗?

呼吸优先搜索

def bfs(target, d_map):
    # Track which values of f(x) have we visited
    visited = set([0])
    # f(x) of the current level of the search tree
    todo = [0]
    # Digits of x for the current level of the search tree
    paths = [[0] * 10]
    while True:
        new_visited = set()
        # Discard old visited every 9 level.
        # This is because the worst case for pruning is
        # nine steps of 8! finally is equivalent to 1
        # step of 9!
        for i in range(9):
            # For holding new nodes of the next level:
            new_todo = []
            new_paths = []
            # Visit the next level
            for old_fx, old_digits in zip(todo, paths):
                # Visit the 9 new digits in the order of
                # large to small. This is because reaching
                # the target with just one big digit is
                # better than reaching the target with
                # several small digits. For example,
                # f(9) is same as f(888888888) but
                # x = 9 is definite smaller than
                # x = 888888888. Therefore, we visit
                # the big digits first.
                for d in [9, 6, 5, 3, 2, 1]:
                    new_fx = old_fx + d_map[d]
                    if new_fx not in visited:
                        # Update the set of visited values of f(x)
                        new_visited.add(new_fx)  # for pruning visited
                        visited.add(new_fx)
                        # Make digits for the new x
                        new_p = old_digits.copy()
                        new_p[d] += 1
                        # Record the digits of the new x
                        new_todo.append(new_fx)
                        new_paths.append(new_p)
                    # Stop if we reach our target
                    if new_fx == target:
                        return new_p
                # Discard record of the nodes of the previous level.
                todo = new_todo
                paths = new_paths
        visited = new_visited  # prune visited every 11 levels


def main():
    # map a digit to f(digit)
    d_map = {0: 1, 1: 1, 2: 2, 3: 6, 4: 6, 5: 3, 6: 9, 7: 9, 8: 9, 9: 27}
    d_map = {1: 1, 2: 2, 3: 6, 5: 3, 6: 9, 9: 27}
    print(bfs(1000000, d_map))
    # [0, 1, 0, 0, 0, 0, 0, 0, 0, 37037]
    # That means 1 one followed by 37037 nines


main()

【问题讨论】:

  • 没有理由使用 4、7 或 8,对吧?这似乎减少到en.wikipedia.org/wiki/Change-making_problem
  • @TavianBarnes 我解决了这个问题。其实也可以去掉0。
  • @TavianBarnes 似乎不太一样。目标是找到最小的x(满足f(x) = n),而不是找到位数最少的x。
  • 这些非常相似,因为数字更多的数字更大。一旦你有一组数字要使用,你就可以按排序顺序写出来。
  • 这很简单。尽可能多地使用 9,并留出 26 的边距以进行优化。

标签: python optimization dynamic-programming mathematical-optimization


【解决方案1】:
def find_x(target):
    t = [0, 1, 2, 5, 15, 25, 3, 13, 23, 6, 16, 
         26, 56, 156, 256, 36, 136, 236, 66, 166, 
         266, 566, 1566, 2566, 366, 1366, 2366]
    r = target % 27
    n_nine = (target // 27) * 3
    if r != 0:
        return str(t[r]) + str(9) * n_nine
    else:
        return str(9) * n_nine

【讨论】:

    【解决方案2】:

    这是我的解决方案。

    def digit_sum (n):
        return sum([int(i) for i in str(n)])
    
    def search (target):
        factorials = [1]
        digit_sums = [0]
        for i in range(1, 10):
            factorials.append(i * factorials[i-1])
            digit_sums.append(digit_sum(factorials[i]))
    
        last_digit = {0: None}
        todo = [0]
        pos = 0
        while target not in last_digit:
            for i in range(1, 10):
                ds = digit_sums[i]
                x = todo[pos] + ds
                if x not in last_digit:
                    last_digit[x] = i
                    todo.append(x)
            pos += 1
        answer = []
        x = target
        while 0 < x:
            answer.append(last_digit[x])
            x -= digit_sums[last_digit[x]]
        return int("".join([str(d) for d in reversed(answer)]))
    
    answer = search(1000000)
    print((answer[0:10], len(answer))
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-12-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-04-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多