这个问题是 NP 完全的,因为它是两个 NP 完全问题的组合:
以下步骤将提供解决方案:
几点说明:
首先,我们知道存在精确覆盖,因为数字列表的总和为 0。
其次,我们只能使用不是任何其他子集的超集的子集。因为,如果A 是X 的超集(两者之和为0),则A 不能在具有最多子集的封面中。让A, B, C, ... 是具有最大子集数的封面,然后我们可以将A 替换为X 和A\X(很容易看出总和A\X 元素中的 0) 我们得到了封面 X, A\X, B, C, ... 这样更好。
第三,当我们使用算法 X 时,搜索树中的所有路径都会导致成功。让A, B, C, ... 是由非重叠子集组成的路径,每个子集的总和为 0。那么补全也有0的总和(它可能是另一个子集的超集,然后我们将使用2。)。
如您所见,这里没有什么新东西,我将只使用众所周知的技术/算法。
找出总和为0 的子集。
算法是众所周知的。这是一个基于Wikipedia explanations的Python实现
class Q:
def __init__(self, values):
self.len = len(values)
self.min = sum(e for e in values if e <= 0)
self.max = sum(e for e in values if e >= 0)
self._arr = [False] * self.len * (self.max - self.min + 1)
def __getitem__(self, item):
index, v = item
return self._arr[v * self.len + index]
def __setitem__(self, item, value):
index, v = item
self._arr[v * self.len + index] = value
class SubsetSum:
def __init__(self, values):
self._values = values
self._q = Q(values)
def prepare(self):
for s in range(self._q.min, self._q.max + 1):
self._q[0, s] = (self._values[0] == s)
for i in range(self._q.len):
self._q[i, 0] = True
for i in range(1, self._q.len):
v = self._values[i]
for s in range(self._q.min, self._q.max + 1):
self._q[i, s] = (v == s) or self._q[i - 1, s] or self._q[
i - 1, s - v]
def subsets(self, target=0):
yield from self._subsets(self._q.len - 1, target, [])
def _subsets(self, i, target, p):
assert i >= 0
v = self._values[i]
c = self._q[i - 1, target]
b = self._q[i - 1, target - v]
if i == 0:
if target == 0:
if p:
yield p
elif self._q[0, target]:
yield p + [i]
else:
if self._q.min <= target - v <= self._q.max and self._q[
i - 1, target - v]:
yield from self._subsets(i - 1, target - v, p + [i])
if self._q[i - 1, target]:
yield from self._subsets(i - 1, target, p)
它是这样工作的:
arr = [-10, 1, 2, 20, 5, -100, -80, 10, 15, 15, 60, 100, -20, -18]
arr = sorted(arr)
s = SubsetSum(arr)
s.prepare()
subsets0 = list(s.subsets())
print(subsets0)
输出:
[[13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], [13, 12, 11, 10, 9, 7, 6, 5, 3, 2, 1, 0], [13, 12, 11, 10, 9, 4, 2, 1, 0], [13, 12, 11, 10, 8, 7, 4, 2, 1, 0], [13, 12, 11, 10, 8, 6, 5, 4, 3, 1, 0], [13, 12, 11, 10, 7, 2, 1, 0], [13, 12, 11, 10, 6, 5, 3, 1, 0], [13, 12, 11, 9, 8, 7, 4, 2, 1, 0], [13, 12, 11, 9, 8, 6, 5, 4, 3, 1, 0], [13, 12, 11, 9, 7, 2, 1, 0], [13, 12, 11, 9, 6, 5, 3, 1, 0], [13, 12, 11, 8, 7, 6, 5, 3, 1, 0], [13, 12, 11, 8, 4, 1, 0], [13, 12, 11, 1, 0], [13, 12, 10, 9, 8, 7, 6, 5, 4, 3, 1, 0], [13, 12, 10, 9, 8, 2, 1, 0], [13, 12, 10, 9, 7, 6, 5, 3, 1, 0], [13, 12, 10, 9, 4, 1, 0], [13, 12, 10, 8, 7, 4, 1, 0], [13, 12, 10, 7, 1, 0], [13, 12, 9, 8, 7, 4, 1, 0], [13, 12, 9, 7, 1, 0], [13, 11, 10, 8, 6, 5, 4, 3, 2, 0], [13, 11, 10, 6, 5, 3, 2, 0], [13, 11, 9, 8, 6, 5, 4, 3, 2, 0], [13, 11, 9, 6, 5, 3, 2, 0], [13, 11, 8, 7, 6, 5, 3, 2, 0], [13, 11, 8, 4, 2, 0], [13, 11, 7, 6, 5, 4, 3, 2, 1], [13, 11, 7, 6, 5, 4, 3, 0], [13, 11, 2, 0], [13, 10, 9, 8, 7, 6, 5, 4, 3, 2, 0], [13, 10, 9, 7, 6, 5, 3, 2, 0], [13, 10, 9, 4, 2, 0], [13, 10, 8, 7, 4, 2, 0], [13, 10, 8, 6, 5, 4, 3, 2, 1], [13, 10, 8, 6, 5, 4, 3, 0], [13, 10, 7, 2, 0], [13, 10, 6, 5, 3, 2, 1], [13, 10, 6, 5, 3, 0], [13, 9, 8, 7, 4, 2, 0], [13, 9, 8, 6, 5, 4, 3, 2, 1], [13, 9, 8, 6, 5, 4, 3, 0], [13, 9, 7, 2, 0], [13, 9, 6, 5, 3, 2, 1], [13, 9, 6, 5, 3, 0], [13, 8, 7, 6, 5, 3, 2, 1], [13, 8, 7, 6, 5, 3, 0], [13, 8, 4, 2, 1], [13, 8, 4, 0], [13, 7, 6, 5, 4, 3, 1], [13, 2, 1], [13, 0], [12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1], [12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 0], [12, 11, 10, 9, 8, 2, 0], [12, 11, 10, 9, 7, 6, 5, 3, 2, 1], [12, 11, 10, 9, 7, 6, 5, 3, 0], [12, 11, 10, 9, 4, 2, 1], [12, 11, 10, 9, 4, 0], [12, 11, 10, 8, 7, 4, 2, 1], [12, 11, 10, 8, 7, 4, 0], [12, 11, 10, 8, 6, 5, 4, 3, 1], [12, 11, 10, 7, 2, 1], [12, 11, 10, 7, 0], [12, 11, 10, 6, 5, 3, 1], [12, 11, 9, 8, 7, 4, 2, 1], [12, 11, 9, 8, 7, 4, 0], [12, 11, 9, 8, 6, 5, 4, 3, 1], [12, 11, 9, 7, 2, 1], [12, 11, 9, 7, 0], [12, 11, 9, 6, 5, 3, 1], [12, 11, 8, 7, 6, 5, 3, 1], [12, 11, 8, 4, 1], [12, 11, 1], [12, 10, 9, 8, 7, 6, 5, 4, 3, 1], [12, 10, 9, 8, 2, 1], [12, 10, 9, 8, 0], [12, 10, 9, 7, 6, 5, 3, 1], [12, 10, 9, 4, 1], [12, 10, 8, 7, 4, 1], [12, 10, 7, 1], [12, 9, 8, 7, 4, 1], [12, 9, 7, 1], [11, 10, 8, 6, 5, 4, 3, 2], [11, 10, 6, 5, 3, 2], [11, 9, 8, 6, 5, 4, 3, 2], [11, 9, 6, 5, 3, 2], [11, 8, 7, 6, 5, 3, 2], [11, 8, 4, 2], [11, 7, 6, 5, 4, 3], [11, 2], [10, 9, 8, 7, 6, 5, 4, 3, 2], [10, 9, 7, 6, 5, 3, 2], [10, 9, 4, 2], [10, 8, 7, 4, 2], [10, 8, 6, 5, 4, 3], [10, 7, 2], [10, 6, 5, 3], [9, 8, 7, 4, 2], [9, 8, 6, 5, 4, 3], [9, 7, 2], [9, 6, 5, 3], [8, 7, 6, 5, 3], [8, 4]]
减少子集的数量
我们有 105 个子集的总和为 0,但我们可以删除作为其他子集超集的子集。我们需要一个函数来查找元素列表是否包含另一个列表中的所有元素。在 Python 中:
import collections
def contains(l1, l2):
"""
Does l1 contain all elements of l2?
"""
c = collections.Counter(l1)
for e in l2:
c[e] -= 1
return all(n >= 0 for n in c.values())
现在,我们可以删除作为另一个子集的超集的子集。
def remove_supersets(subsets):
subsets = sorted(subsets, key=len)
new_subsets = []
for i, s1 in enumerate(subsets):
for s2 in subsets[:i]: # smaller subsets
if contains(s1, s2):
break
else: # not a superset
new_subsets.append(s1)
return new_subsets
在我们的情况下:
subsets0 = remove_supersets(subsets0)
print(len(subsets0))
输出:
[[13, 0], [11, 2], [8, 4], [13, 2, 1], [12, 11, 1], [10, 7, 2], [9, 7, 2], [12, 10, 7, 1], [12, 9, 7, 1], [10, 9, 4, 2], [10, 6, 5, 3], [9, 6, 5, 3], [12, 11, 10, 7, 0], [12, 11, 9, 7, 0], [12, 10, 9, 8, 0], [12, 10, 9, 4, 1], [8, 7, 6, 5, 3], [12, 11, 10, 9, 4, 0], [12, 10, 9, 8, 2, 1], [11, 7, 6, 5, 4, 3], [13, 7, 6, 5, 4, 3, 1]]
[[0, 2, 10, 6, 4], [0, 2, 10, 8, 1], [0, 2, 11, 5, 4], [0, 2, 11, 7, 1], [0, 16, 9, 4], [0, 16, 15, 1], [0, 18, 19], [3, 2, 12, 11], [3, 2, 13, 10], [3, 17, 16], [3, 19, 14], [20, 14, 1]]
我们设法将子集的数量减少到 21 个,这是一个很好的改进,因为我们需要探索所有可能性来找到准确的覆盖。
算法 X
我不在这里使用跳舞链接(我认为我们将为 C 等低级语言设计该技术,但如果您愿意,您可以在 Python 中实现它们)。我们只需要跟踪剩余的子集:
class Matrix:
def __init__(self, subsets, ignore_indices=set()):
self._subsets = subsets
self._ignore_indices = ignore_indices
def subset_values(self, i):
assert i not in self._ignore_indices
return self._subsets[i]
def value_subsets_indices(self, j):
return [i for i, s in self._subsets_generator() if j in s]
def _subsets_generator(self):
return ((i, s) for i, s in enumerate(self._subsets) if
i not in self._ignore_indices)
def rarest_value(self):
c = collections.Counter(
j for _, s in self._subsets_generator() for j in s)
return c.most_common()[-1][0]
def take_subset(self, i):
s = self._subsets[i]
to_ignore = {i2 for i2, s2 in self._subsets_generator() if
set(s2) & set(s)}
return Matrix(self._subsets,
self._ignore_indices | to_ignore)
def __bool__(self):
return bool(list(self._subsets_generator()))
最后是cover 函数:
def cover(m, t=[]):
if m: # m is not empty
j = m.rarest_value()
for i in m.value_subsets_indices(j):
m2 = m.take_subset(i)
yield from cover(m2, t + [i])
else:
yield t
最后,我们有:
m = Matrix(subsets0)
ts = list(cover(m))
t = max(ts, key=len)
print([[arr[j] for j in subsets0[i]] for i in t])
输出:
[[100, -100], [10, -10], [15, 2, 1, -18], [15, 5, -20], [60, 20, -80]]