【问题标题】:Finding a subset of numbers that will give you the max sum找到可以给你最大总和的数字子集
【发布时间】:2017-11-17 10:34:29
【问题描述】:

如何找到一个从 2 到 1000 的数字子集,在该子集中的任何两个数字不共享共同的质因数(例如,1000 和 500 共享质因数 2)的条件下,它会给您最大和?

上述问题的一个(可能更容易)变体:子集中的最大数字是多少?我们知道 997 是一个素数,很容易排除 1000 和 998,那么问题就变成了 999 是否在子集中?

【问题讨论】:

  • 这是一个难题。您应该在这里使用动态编程技术。在算法结束之前,您无法知道您是否选择了正确的数字,因此您需要找到方法从可能的选择中消除数字,然后再选择它们并计算新的总和。但首先,您可以开始选择从sqrt(1000)1000 的所有素数,然后只比较不在该集合中的素数。
  • “排除1000和998很容易”真的吗?为什么?
  • @Henrik:因为它们有一个共同的因数,即 2。
  • euclidean algorithm for the greatest common divisor function 是一种快速算法,用于确定任何两个整数是否共享一个公因数。您正在寻找总和最大化的成对相对素数的子集。一个这样的集合是显而易见的:[2...1000] 中所有素数的集合。请注意,使用欧几里得算法可能不是这个问题的最佳选择;根据您的解决方案,最好使用筛子完全分解每个整数。
  • @Henrik: 1000=2^3 * 5^3,但 512 + 625 > 1000; 998=2*499,但 512 + 499 > 1000。

标签: math max primes combinatorics subset-sum


【解决方案1】:

创建一个包含节点 {2, ..., 1000} 和边的图,当节点的 gcd > 1 时。解决此问题的方法与查找 maximum-weight independent set problem 相同,除了某些特殊情况外,这是 NP-hard。这个图表案例看起来不像维基百科页面上的列表中的空间案例,甚至在这个list 上。

更新

正如 James 所建议的,可以减少此图中的节点数量。假设具有素数分解p1^k1*...*pn^kn 的数字N 的签名是一个元组(p1, ..., pn)

当存在具有较大值和相同签名的节点时,首先减少节点。这将图形减少到 607 个节点。

如果存在签名为(p1, ..., pn) 分解的节点并且总和为>= N,则下一个缩减是删除签名为(p1, ..., pn) 的节点N。这会将图形减少到 277 个节点。

从这些节点中,有 73 个是孤立的节点(素数 > 500。)

【讨论】:

  • 边缘在什么之间?
  • @JamesKPolk 节点之间的边。例如。每对偶数节点之间都有一条边,例如 1000 和 998。
  • 这似乎是正确的,+1。我一直在玩 python igraph,似乎这个图的大小太大而无法在我的 macbook 上解决,而节点 {2, 3, ..., 300} 的相同问题可以在大约 6 分钟内解决CPU 和 6 GB 内存。如果有一些逻辑可以先验地确定最大和中的偶数,我们可以立即消除至少一半的节点。
  • @JamesKPolk 消除的好主意。如果我们的节点 N 的值为 p_1^k_1 * ... * p_n^k_n,其中 p 是素数,k >= 1。那么在分解中不需要具有相同素数的节点,其中所有 k_i 都小于或等于N,但 >= 1,且 1 严格小于。比如,如果我们有 512,则不需要 256。或者如果我们有 12 或 18,则不需要 6。
  • @JamesKPolk 这种减少将留下 168 个 p^k 类型的数字、288 个 p1^k1*p2^k2 类型的数字、135 个具有三个素数的节点和 16 个具有 4 个素数的节点。那是 607 个节点,其中 73 个节点根本没有连接(素数 > 500)。图形将比原始图形更稀疏,这可以获得加速。
【解决方案2】:

我不知道这个问题的答案,这对我来说似乎很重要。以下是一些想法。

总和由可变数量的和组成,例如 s0 + s1 + ... sk,其中 si 是区间 [2, 1000] 中的整数。现在每个 si 都有一个素数功率因数分解 si=(p1e1)*(p2e2) ... 其中 ei ≥ 1.

“子集中任意两个数不具有公共素因数”的条件等价于说明 si 是成对互素的,即 gcd(si, sj)=1 对于 i ≠ j。同样等价地,只要一个加数 si 包含一个素数 p,这意味着没有其他加数可能包含该素数。

那么如何将素数排列成加法?一个简单的规则是显而易见的。 [500, 1000] 中的所有素数只能作为单独的被加数单独出现在和中。如果将它们乘以其他任何值,即使是最小的素数 2,乘积也会太大。这样就剩下安排较小素数的任务了。而且我不知道他们最好的方式来做到这一点。为了完整起见,我将提供以下简短的 Python 程序来展示一种方式。

def sieve_prime_set(n):
    # sieve[i] = set(p1, p2, ...pn) for each prime p_i that divides i.

    sieve = [set() for i in range(n + 1)]
    primes = []
    next_prime = 1
    while True:
        # find the next prime
        for i in range(next_prime + 1, len(sieve)):
            if not sieve[i]:
                next_prime = i
                break
        else:
            break

        primes.append(next_prime)
        # sieve out by this prime
        for kp in range(next_prime, n + 1, next_prime):
            sieve[kp].add(next_prime)

    return sieve, primes

def max_sum_strategy1(sieve):
    last = len(sieve) - 1
    summands = [last]
    max_sum = last
    prime_set = sieve[last]
    while last >= 2:
        last -= 1
        if not sieve[last] & prime_set:
            max_sum += last
            prime_set |= sieve[last]
            summands.append(last)

    return max_sum, summands, prime_set

def max_sum_strategy2(primes, n):
    return sum(p ** int(log(n, p)) for p in primes)



if __name__ == '__main__':
    sieve, primes = sieve_prime_set(1000)
    max_sum, _, _ = max_sum_strategy1(sieve)
    print(max_sum)
    print(max_sum_strategy2(primes, 1000))

输出是

84972
81447

表明“策略 1”更胜一筹。

优越,但不一定是最佳的。例如,包括 1000 看起来不错,但它迫使我们排除所有其他偶数和所有可被 5 整除的和数。如果我们省略 1000 但包含 998,我们将使用另一个在素因数分解中包含 5 的被数。但包括 998 会强制排除其他加法。所以最大化总和并非易事。

【讨论】:

    【解决方案3】:

    这是一个解决问题的 Python 脚本。在我的笔记本电脑上运行需要几分钟。

    import math
    import networkx as nx
    
    max_number = 1000
    
    G = nx.Graph()
    
    for i in range(2, max_number + 1):
        G.add_node(i, weight=i)
    
    for i in range(2, max_number + 1):
        for j in range(i+1, max_number + 1):
            if math.gcd(i, j) == 1:
                G.add_edge(i, j)
    
    numbers, sum_of_numbers = nx.max_weight_clique(G)
    
    print(sorted(numbers))
    print(sum_of_numbers)
    

    返回的数字列表如下所示;总数是 85684。(可能有其他有效的数字集给出这个总数。)

    [41, 59, 67, 71, 79, 83, 97, 101, 103, 107, 113, 127, 131, 137, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 841, 853, 857, 859, 863, 877, 881, 883, 887, 893, 901, 907, 911, 919, 925, 929, 937, 941, 947, 949, 953, 961, 967, 971, 973, 976, 977, 979, 981, 983, 989, 991, 997]
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-06-19
      • 1970-01-01
      • 2017-08-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多