我正在尝试解决一个问题,该问题已简化为计算多个线性不等式的整数解的数量。我需要能够计算任意数量的变量 c_1、...、c_n 的解数,但对于 n=3,方程可以写为:
方程。http://silicon.appspot.com/readdoc?id=155604
现在,我提前知道 n 和 r 的值,并希望找到存在的 (c_1, ..., c_n) 解决方案的数量。
这可以有效地完成(比列举解决方案更快)吗?(如果是:如何?;如果不是:为什么?)
我正在尝试解决一个问题,该问题已简化为计算多个线性不等式的整数解的数量。我需要能够计算任意数量的变量 c_1、...、c_n 的解数,但对于 n=3,方程可以写为:
方程。http://silicon.appspot.com/readdoc?id=155604
现在,我提前知道 n 和 r 的值,并希望找到存在的 (c_1, ..., c_n) 解决方案的数量。
这可以有效地完成(比列举解决方案更快)吗?(如果是:如何?;如果不是:为什么?)
为了解决这个问题,我可能会进入约束编程领域。看起来你有一个经典的all different
约束(有点像 N-Queens 问题)。试试下面列出的免费约束求解器之一。这将为您提供非常有效的解决方案。它基本上会生成整个搜索树,但是有了很好的 All-Different 约束实现,该树最终将被修剪得几乎为零。
http://www.gecode.org/ http://minion.sourceforge.net/ http://jacop.osolpro.com/ http://research.microsoft.com/apps/pubs/default.aspx?id= 64335
这并不是您问题的完整解决方案,但我认为它可能会有所帮助或至少给您一些想法。
您要求解决方案是整数使得这是一个 NP 问题。如果我们首先考虑问题的松弛以使域是实数,那么您要求解决 0 <= A*c <= 1 的可满足性问题,其中 A 是矩阵,c 是未知数向量。这是一个标准的线性程序(具有平凡目标的 LP),并且可以有效地求解(在多项式时间内)。您可能希望将此作为第一次通过测试来确定可行性,因为如果松弛 LP 没有解决方案,那么您的整数 LP 肯定没有解决方案。如果可能的话,一个好的 LP 求解器也会返回一个可行点,并且您可以对向量的条目进行四舍五入以找到一个整数解。
假设您有一些代码来生成所有解决方案。
(对于此处的参数z,传递 9。它是不等式右侧的数字。请注意,此代码仅在r为正时有效。)
from math import floor, ceil
def iter_solutions(r, n, z):
c = [None] * n
def iter_solutions_bounded(k, pick):
# pick is the last pick, if any, and 0 otherwise
assert (1 <= k < n and pick == c[k]) or (k == n and pick == 0)
min_ck = int(ceil(-pick / r))
max_ck = int(floor((z - pick) / r))
if k == 1:
for ck in range(max(min_ck, 0), min(max_ck, z) + 1):
c[0] = ck
yield c
else:
for ck in range(min_ck, max_ck + 1):
c[k - 1] = ck
for soln in iter_solutions_bounded(k - 1, ck):
yield soln
return iter_solutions_bounded(n, 0)
您可以将其转换为仅计算解决方案的代码,只需删除所有引用的代码c
并将本应产生的解决方案的数量相加即可。最后,您可以通过记忆来提高性能。
from math import floor, ceil
def memoize(f):
cache = {}
def g(*args):
if args in cache:
return cache[args]
tmp = cache[args] = f(*args)
return tmp
return g
def len_range(a, b):
if a <= b:
return b - a
return 0
def count_solutions(r, n, z):
@memoize
def count_solutions_bounded(k, pick):
min_ck = int(ceil(-pick / r))
max_ck = int(floor((z - pick) / r))
if k == 1:
return len_range(max(min_ck, 0), min(max_ck, z) + 1)
else:
return sum(count_solutions_bounded(k - 1, ck) for ck in range(min_ck, max_ck + 1))
return count_solutions_bounded(n, 0)
一些可能的改进:
如果确实c 1 ... c n总是 ≤ z ,那么检测到它并立即返回 0 对于大n会有很大帮助。事实上,它会将运行时间减少到快如闪电的 O( nz )。
如果打算使c 1 ... c n都为非负数,那就更好了。min_ck
对and进行适当的更改max_ck
将使这个 O( nz ) 具有更小的常数,并且缓存可以是一个平面 2D 数组,而不是我得到的较慢的哈希表。
您可以通过系统地构建缓存来做得更好,而不是像这个记忆代码那样“按需”填充它。首先为 n=1 构建整个缓存,然后为 n=2 构建整个缓存,依此类推。这样您就可以避免递归,并且在每一步都可以丢弃不再需要的缓存数据(在计算 n=2 的结果之后,您不再需要 n=1 的条目)。
正如其他人所提到的,如果您想根据这些约束最大化线性目标函数,那么您将遇到整数线性规划问题,对此不存在有效的一般解决方案。相反,您似乎在询问可行区域中的点数,这是一个不同的问题,但由于必须有整数解,它也很复杂。
我能想出的最好办法是找到可行区域边界上的点,并使用这些点来确定内部点的数量。这对于加速低维中的“计算晶格点”类型的问题非常有效,但边界仍然只比所讨论的体积小一维。如果你的问题超过了几个维度,那么问题仍然是棘手的,即使它比枚举所有解决方案要快。