一个列表(可能)可以被另一个整除吗?

Posted

技术标签:

【中文标题】一个列表(可能)可以被另一个整除吗?【英文标题】:Is a list (potentially) divisible by another? 【发布时间】:2018-02-05 00:00:18 【问题描述】:

问题

假设您有两个整数列表 A = [a_1, a_2, ..., a_n]B = [b_1, b_2, ..., b_n]。如果B 的排列使得a_i 对于所有i 都可以被b_i 整除,我们就说A 可以被B 整除。那么问题是:是否可以重新排序(即置换)B,以便所有ia_i 可以被b_i 整除? 例如,如果您有

A = [6, 12, 8]
B = [3, 4, 6]

那么答案将是True,因为B 可以重新排序为B = [3, 6, 4],然后我们将得到a_1 / b_1 = 2a_2 / b_2 = 2a_3 / b_3 = 2,它们都是整数,所以A 可以被B 整除。

作为一个应该输出False的例子,我们可以有:

A = [10, 12, 6, 5, 21, 25]
B = [2, 7, 5, 3, 12, 3]

这是False 的原因是我们不能对B 重新排序,因为A 中有25 和5,但B 中唯一的除数是5,所以会省略一个。

方法

显然,直接的方法是获取B 的所有排列,看看是否满足potential-divisibility,类似于:

import itertools
def is_potentially_divisible(A, B):
  perms = itertools.permutations(B)
  divisible = lambda ls: all( x % y == 0 for x, y in zip(A, ls))
  return any(divisible(perm) for perm in perms)

问题

知道一个列表是否可以被另一个列表整除的最快方法是什么?有什么想法吗?我在想是否有一个聪明的方法可以用 primes 做到这一点,但我想不出一个解决方案。

非常感谢!


编辑:这可能与你们大多数人无关,但为了完整起见,我将解释我的动机。在群论中,有一个关于有限单群的猜想,即群的不可约特征和共轭类是否存在双射,使得每个特征度都划分相应的类大小。例如,对于 U6(4) here are what A and B would look like. 相当大的列表,请注意!

【问题讨论】:

有一个(现已删除)答案声称对两个列表进行排序是有效解决方案的一部分。这有什么问题吗? @cᴏʟᴅsᴘᴇᴇᴅ 我不知道为什么答案被删除了。我也不知道排序在这里是否会有所帮助。 @McGuire:a_ib_i 有多大? @Blender 理想的任意大 是否允许在任一列表中重复?例如B 可以是[1,1,1] 【参考方案1】:

构建二分图结构 - 将 a[i] 与其来自 b[] 的所有除数连接起来。

然后找到maximum matching并检查它是否是完美匹配(匹配中的边数等于对数(如果图是有向的)或双数)。

任意选择Kuhn algorithm implementation here。

更新: @Eric Duminil 非常简洁 Python implementation here

这种方法的多项式复杂度从 O(n^2) 到 O(n^3),具体取决于所选择的匹配算法和边数(除法对)与蛮力算法的阶乘复杂度。

【讨论】:

@cᴏʟᴅsᴘᴇᴇᴅ:链接只是额外的文档。没有它们,答案仍然有效,因为在***或许多书籍上仍然可以找到提到的图论术语。 要检测完美匹配,使用高斯消元法找到Tutte matrix的行列式,然后检查是否非零。【参考方案2】:

代码

在@MBo 的优秀answer 的基础上,这是一个使用networkx 的二分图匹配实现。

import networkx as nx

def is_potentially_divisible(multiples, divisors):
    if len(multiples) != len(divisors):
        return False

    g = nx.Graph()
    g.add_nodes_from([('A', a, i) for i, a in enumerate(multiples)], bipartite=0)
    g.add_nodes_from([('B', b, j) for j, b in enumerate(divisors)], bipartite=1)

    edges = [(('A', a, i), ('B', b, j)) for i, a in enumerate(multiples)
             for j, b in enumerate(divisors) if a % b == 0]
    g.add_edges_from(edges)
    m = nx.bipartite.maximum_matching(g)
    return len(m) // 2 == len(multiples)

print(is_potentially_divisible([6, 12, 8], [3, 4, 6]))
# True
print(is_potentially_divisible([6, 12, 8], [3, 4, 3]))
# True
print(is_potentially_divisible([10, 12, 6, 5, 21, 25], [2, 7, 5, 3, 12, 3]))
# False

注意事项

根据documentation:

maximum_matching() 返回的字典包含一个映射 左右顶点集中的顶点。

表示返回的dict应该是AB的两倍。

节点转换自

[10, 12, 6, 5, 21, 25]

到:

[('A', 10, 0), ('A', 12, 1), ('A', 6, 2), ('A', 5, 3), ('A', 21, 4), ('A', 25, 5)]

为了避免来自AB 的节点之间的冲突。还添加了 id 以在重复的情况下保持节点不同。

效率

maximum_matching 方法使用Hopcroft-Karp algorithm,在最坏的情况下以O(n**2.5) 运行。图生成是O(n**2),所以整个方法运行在O(n**2.5)。它应该适用于大型阵列。置换解决方案是O(n!),将无法处理包含 20 个元素的数组。

附图表

如果您对显示最佳匹配的图表感兴趣,可以混合使用 matplotlib 和 networkx:

import networkx as nx
import matplotlib.pyplot as plt

def is_potentially_divisible(multiples, divisors):
    if len(multiples) != len(divisors):
        return False

    g = nx.Graph()

    l = [('l', a, i) for i, a in enumerate(multiples)]
    r = [('r', b, j) for j, b in enumerate(divisors)]

    g.add_nodes_from(l, bipartite=0)
    g.add_nodes_from(r, bipartite=1)

    edges = [(a,b) for a in l for b in r if a[1] % b[1]== 0]
    g.add_edges_from(edges)

    pos = 
    pos.update((node, (1, index)) for index, node in enumerate(l))
    pos.update((node, (2, index)) for index, node in enumerate(r))

    m = nx.bipartite.maximum_matching(g)
    colors = ['blue' if m.get(a) == b else 'gray' for a,b in edges]

    nx.draw_networkx(g, pos=pos, arrows=False, labels = n:n[1] for n in g.nodes(), edge_color=colors)
    plt.axis('off')
    plt.show()

    return len(m) // 2 == len(multiples)

print(is_potentially_divisible([6, 12, 8], [3, 4, 6]))
# True
print(is_potentially_divisible([6, 12, 8], [3, 4, 3]))
# True
print(is_potentially_divisible([10, 12, 6, 5, 21, 25], [2, 7, 5, 3, 12, 3]))
# False

下面是对应的图表:

【讨论】:

【参考方案3】:

既然您对数学很熟悉,我只想为其他答案增添光彩。要搜索的字词以粗体显示。

问题是位置受限的排列的一个实例,关于这些可以说很多。通常,当且仅当位置j 允许最初位于位置i 的元素时,可以构造一个零-一NxN 矩阵M,其中M[i][j] 为1。满足所有限制的不同排列的数量M永久(定义与行列式相同,除了所有项都是非负数)。

唉 - 与行列式不同 - 在N 中,没有已知的通用方法可以比指数计算更快。但是,有多项式时间算法可以确定永久物是否为 0。

这就是您得到答案的地方开始 ;-) 这里很好地说明了“永久 0 是什么?”通过考虑二部图中的完美匹配可以有效地回答问题:

https://cstheory.stackexchange.com/questions/32885/matrix-permanent-is-0

因此,在实践中,您不太可能找到比@Eric Duminil 在他们的答案中给出的更快的通用方法。

注意,稍后补充:我应该让最后一部分更清楚。给定任何“受限置换”矩阵M,很容易构造与其对应的整数“除数表”。因此,您的具体问题并不比一般问题容易 - 除非您的列表中可能出现的整数有什么特别之处。

例如,假设M

0 1 1 1
1 0 1 1
1 1 0 1
1 1 1 0

查看代表前4个素数的行,这也是B中的值:

B = [2, 3, 5, 7]

然后第一行“说”B[0] (= 2) 不能除以 A[0],但必须除以 A[1]A[2]A[3]。等等。通过构造,

A = [3*5*7, 2*5*7, 2*3*7, 2*3*5]
B = [2,     3,     5,     7]

对应于M。还有permanent(M) = 9 置换B 的方法,这样A 的每个元素都可以被置换后的B 的相应元素整除。

【讨论】:

【参考方案4】:

这不是最终的答案,但我认为这可能是有价值的。您可以首先列出列表[(1,2,5,10),(1,2,3,6,12),(1,2,3,6),(1,5),(1,3,7,21),(1,5,25)] 中所有元素的因子(包括1 和自身)。我们要查找的列表必须包含其中的一个因素(以均分)。 由于我们正在检查的列表中没有某些因素([2,7,5,3,12,3]),因此该列表可以进一步过滤为:

[(2,5),(2,3,12),(2,3),(5),(3,7),(5)]

这里,有两个地方需要 5(我们根本没有任何选择),但我们只有 5,所以,我们几乎可以在这里停下来,说这里的情况是假的。

假设我们有[2,7,5,3,5,3]

那么我们会有这样的选择:

[(2,5),(2,3),(2,3),(5),(3,7),(5)]

因为有两个地方需要 5:

[(2),(2,3),(2,3),5,(3,7),5] 其中 表示确保位置。

也保证了 2 个:

[2,(2,3),(2,3),5,(3,7),5]现在既然取了2,就保证了3的两个地方:

[2,3,3,5,(3,7),5] 现在当然是 3 和 7 确保:

[2,3,3,5,7,5]。这仍然与我们的列表一致,因此案例是正确的。请记住,我们将在每次可以轻松突破的迭代中查看与列表的一致性。

【讨论】:

【参考方案5】:

你可以试试这个:

import itertools

def potentially_divisible(A, B):
    A = itertools.permutations(A, len(A))
   return len([i for i in A if all(c%d == 0 for c, d in zip(i, B))]) > 0

l1 = [6, 12, 8]
l2 = [3, 4, 6]

print(potentially_divisible(l1, l2))

输出:

True

另一个例子:

l1 = [10, 12, 6, 5, 21, 25]
l2 = [2, 7, 5, 3, 12, 3]

print(potentially_divisible(l1, l2))

输出:

False

【讨论】:

这与 OP 的方法有何不同? @cᴏʟᴅsᴘᴇᴇᴅ 我同意,我正在寻找另一种方法

以上是关于一个列表(可能)可以被另一个整除吗?的主要内容,如果未能解决你的问题,请参考以下文章

检查一个数字是不是可以被另一个整除的快速方法?

如何在C#中检查数字是不是可以被另一个数字整除而没有条件检查(如果,三进制等)

R可以导致文件被另一个程序打开吗?

整除问题

整除问题

Blender中是不是有一个“保持在表面之上”的功能,可以向外移动可能被另一个对象隐藏的对象的任何部分?