Является ли список (потенциально) делимым другим?
Проблема
Скажем, у вас есть два списка A = [a_1, a_2, ..., a_n]
и B = [b_1, b_2, ..., b_n]
целых чисел. Скажем, A
потенциально-делимый на B
, если существует перестановка B
, которая делает a_i
делимой на b_i
для всех i
. Тогда возникает проблема: можно ли переупорядочить (т.е. Переставить) B
, чтобы a_i
делится на b_i
для всех i
?
Например, если у вас есть
A = [6, 12, 8]
B = [3, 4, 6]
Тогда ответ будет True
, так как B
можно переупорядочить как B = [3, 6, 4]
, а затем мы будем иметь a_1 / b_1 = 2
, a_2 / b_2 = 2
и a_3 / b_3 = 2
, все из которых являются целыми числами, поэтому A
является потенциально делящимся на B
.
В качестве примера, который должен выводить False
, мы могли бы иметь:
A = [10, 12, 6, 5, 21, 25]
B = [2, 7, 5, 3, 12, 3]
Причина, по которой это False
, заключается в том, что мы не можем изменить порядок B
, так как 25 и 5 находятся в A
, но единственным делителем в B
будет 5, поэтому можно было бы оставить без изменений.
Подход
Очевидно, что простой подход состоял бы в том, чтобы получить все перестановки B
и посмотреть, будет ли удовлетворять делимости потенциала, что-то вроде строк:
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)
Вопрос
Каков самый быстрый способ узнать, является ли список потенциально делящимся другим списком? Есть предположения? Я думал, если есть умный способ сделать это с помощью простых чисел, но я не смог найти решение.
Очень ценно!
Изменить: Это, вероятно, не имеет отношения к большинству из вас, но, ради полноты, я объясню свою мотивацию. В теории групп существует гипотеза о конечных простых группах о том, существует ли биекция из неприводимых характеров и классов сопряженности группы такая, что каждая степень характера делит соответствующий размер класса. Например, для U6 (4) вот как выглядят A
и B
. Довольно большие списки, заметьте!
Ответы
Ответ 1
Создайте двухстороннюю структуру графа - соедините a[i]
со всеми своими делителями от b[]
.
![введите описание изображения здесь]()
Затем найдите максимальное соответствие и проверьте, соответствует ли оно совершенному соответствию (количество ребер в сопоставлении равно количество пар (если граф направлен) или удвоенное число).
Произвольная выбранная реализация алгоритма Kuhn здесь.
Upd:
@Eric Duminil сделал замечательную реализацию Python здесь
Этот подход имеет полиномиальную сложность от O (n ^ 2) до O (n ^ 3) в зависимости от выбранного алгоритма соответствия и числа ребер (пар деления) от факторной сложности для алгоритма грубой силы.
Ответ 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 должен быть в два раза больше, чем A
и B
.
Узлы преобразуются из
[10, 12, 6, 5, 21, 25]
в
[('A', 10, 0), ('A', 12, 1), ('A', 6, 2), ('A', 5, 3), ('A', 21, 4), ('A', 25, 5)]
чтобы избежать столкновений между узлами из A
и B
. Идентификатор также добавляется, чтобы сохранить узлы в случае дубликатов.
Эффективность
В методе maximum_matching
используется алгоритм Хопкрофта-Карпа, который работает в 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
Поскольку вам нравится математика, я просто хочу добавить блеск к другим ответам. Условия поиска показаны в жирным шрифтом.
Проблема - это экземпляр перестановок с ограниченными позициями, и там много чего можно сказать о них. В общем случае можно построить матрицу NxN
с нулевым значением NxN
, где M[i][j]
равно 1, если и только если позиция j
разрешена для элемента, первоначально находящегося в позиции i
. Число различных перестановок, удовлетворяющих всем ограничениям, является постоянным 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 и сам включенный) из всех элементов в списке [(1,2,5,10),(1,2,3,6,12),(1,2,3,6),(1,5),(1,3,7,21),(1,5,25)]
. В списке, который мы ищем, должен быть один из факторов (равномерно распределить).
Поскольку у нас нет некоторых факторов в списке, мы проверяем arre против ([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