找到两个线性等式成立的整数集

时间:2016-03-30 23:28:49

标签: java c# python algorithm language-agnostic

我可以使用什么算法来查找n1, n2, ... ,n7的所有正整数值的集合,其中以下不等式成立。

97n1 + 89n2 + 42n3 + 20n4 + 16n5 + 11n6 + 2n7 - 185 > 0
-98n1 - 90n2 - 43n3 - 21n4 - 17n5 - 12n6 - 3n7 + 205 > 0
n1 >= 0, n2 >= 0, n3 >=0. n4 >=0, n5 >=0, n6 >=0, n7 >= 0

例如,一组n1= 2, n2 = n3 = ... = n7 =0使不等式成立。我如何找出所有其他值集?类似的问题已发布在M.SE

ADDED :: 我需要概括n个变量的解决方案(可能很大)。我可以申请什么程序?对于另一个特定情况n=8

97n1 + 89n2 + 42n3 + 20n4 + 16n5 + 11n6 + 6n7 + 2n8 - 185 > 0
-98n1 - 90n2 - 43n3 - 21n4 - 17n5 - 12n6  - 7 - 3n8 + 205 > 0
n1 >= 0, n2 >= 0, n3 >=0. n4 >=0, n5 >=0, n6 >=0, n7 >= 0, n8 >= 0

Python需要永远。 Wolfram Mathematica显示在不到一分钟内就有4015个解决方案。

Length[Solve[{97 n1 + 89 n2 + 42 n3 + 20 n4 + 16 n5 + 11 n6 + 6 n7 + 
     2 n8 - 185 > 0, 
   -98 n1 - 90 n2 - 43 n3 - 21 n4 - 17 n5 - 12 n6 - 7 n7 - 3 n8 + 
     205 > 0,
   n1 >= 0, n2 >= 0, n3 >= 0, n4 >= 0, n5 >= 0, n6 >= 0, n7 >= 0, 
   n8 >= 0}, {n1, n2, n3, n4, n5, n6, n7, n8}, Integers]]

6 个答案:

答案 0 :(得分:8)

Reti43有正确的想法,但有一个快速的递归解决方案,可以对你的不等式进行较少限制性的假设。

def solve(smin, smax, coef1, coef2):
    """
    Return a list of lists of non-negative integers `n` that satisfy
    the inequalities,

    sum([coef1[i] * n[i] for i in range(len(coef1)]) > smin
    sum([coef2[i] * n[i] for i in range(len(coef1)]) < smax

    where coef1 and coef2 are equal-length lists of positive integers.
    """
    if smax < 0:
        return []

    n_max = ((smax-1) // coef2[0])
    solutions = []
    if len(coef1) > 1:
        for n0 in range(n_max + 1):
            for solution in solve(smin - n0 * coef1[0],
                                  smax - n0 * coef2[0], 
                                  coef1[1:], coef2[1:]):
                solutions.append([n0] + solution)
    else:
        n_min = max(0, (smin // coef1[0]) + 1)
        for n0 in range(n_min, n_max + 1):
            if n0 * coef1[0] > smin and n0 * coef2[0] < smax:
                solutions.append([n0])
    return solutions

您可以将此应用于原始问题,

smin, coef1 = 185, (97, 89, 42, 20, 16, 11, 2)
smax, coef2 = 205, (98, 90, 43, 21, 17, 12, 3)
solns7 = solve(smin, smax, coef1, coef2)
len(solns7)
1013

以及像这样的长期问题,

smin, coef1 = 185, (97, 89, 42, 20, 16, 11, 6, 2)
smax, coef2 = 205, (98, 90, 43, 21, 17, 12, 7, 3)
solns8 = solve(smin, smax, coef1, coef2)
len(solns8)
4015

在我的Macbook上,这两种情况都在几毫秒内完成。这应该可以很好地扩展到略大的问题,但从根本上说,它是系数N的O(2 ^ N)。实际扩展的程度取决于附加系数的大小 - 更大的系数(与smax-smin相比,解决方案越少,运行速度越快。

更新:从关于链接M.SE post的讨论中,我看到这两个不等式之间的关系是问题结构的一部分。鉴于此,可以给出稍微简单的解决方案。下面的代码还包括一些额外的优化,可以在我的笔记本电脑上将8变量的解决方案从88毫秒加速到34毫秒。我已经尝试过多达22个变量的示例,并在不到一分钟的时间内得到了结果,但对于数百个变量来说,它永远不会实用。

def solve(smin, smax, coef):
    """
    Return a list of lists of non-negative integers `n` that satisfy
    the inequalities,

    sum([coef[i] * n[i] for i in range(len(coef)]) > smin
    sum([(coef[i]+1) * n[i] for i in range(len(coef)]) < smax

    where coef is a list of positive integer coefficients, ordered
    from highest to lowest.
    """
    if smax <= smin:
        return []
    if smin < 0 and smax <= coef[-1]+1:
        return [[0] * len(coef)]

    c0 = coef[0]
    c1 = c0 + 1
    n_max = ((smax-1) // c1)
    solutions = []
    if len(coef) > 1:
        for n0 in range(n_max + 1):
            for solution in solve(smin - n0 * c0,
                                  smax - n0 * c1, 
                                  coef[1:]):
                solutions.append([n0] + solution)
    else:
        n_min = max(0, (smin // c0) + 1)
        for n0 in range(n_min, n_max + 1):
            solutions.append([n0])
    return solutions

您可以将它应用于8变量示例,例如

solutions = solve(185, 205, (97, 89, 42, 20, 16, 11, 6, 2))
len(solutions)
4015

该解决方案直接枚举有界区域中的晶格点。由于您需要所有这些解决方案,因此获取它们所需的时间将与绑定格点的数量成比例(最多),这些格点将随着维度(变量)的数量呈指数增长。

答案 1 :(得分:3)

在你给出的两个例子中,我注意到了相同的模式。例如,对于第一种情况,如果将两个方程式加在一起,那么您将获得

-n1 - n2 - n3 - n4 - n5 - n6 - n7 + 20 > 0

可以重新排列为

n1 + n2 + n3 + n4 + n5 + n6 + n7 < 20

这是一个很好的有限方程,你可以蛮力。具体来说,您可以从0到19迭代n1,从0到19-n1等n2迭代等。这可能的解决方案是(0,0,0,0,0,0) ,0),但我们注意到这并不能满足我们原来的等式。因此,只需为(n1,n2,...,n7)生成所有可能的值,并仅保留满足等式的值。将所有这些结果硬编码到

def find_solutions(N):
    sols = []
    for n1 in xrange(N):
        for n2 in xrange(N-n1):
            for n3 in xrange(N-n1-n2):
                for n4 in xrange(N-n1-n2-n3):
                    for n5 in xrange(N-n1-n2-n3-n4):
                        for n6 in xrange(N-n1-n2-n3-n4-n5):
                            for n7 in xrange(N-n1-n2-n3-n4-n5-n6):
                                if (97*n1 + 89*n2 + 42*n3 + 20*n4 + 16*n5 + 11*n6 + 2*n7 - 185 > 0 and
                                    -98*n1 - 90*n2 - 43*n3 - 21*n4 - 17*n5 - 12*n6 - 3*n7 + 205 > 0):
                                    sols.append((n1, n2, n3, n4, n5, n6, n7))
return sols

find_solutions(20)在0.6秒内找到所有1013个解决方案。同样,对于第二种情况,它在2.3秒内找到所有4015解决方案。现在,这根本不容易概括,但它表明,使用智能方法,Python或任何其他语言,并不一定很慢。

另一方面,递归允许我们对任意数量的嵌套循环进行推广,但代价是运行速度稍慢。

def find_solutions(N, coeffs, depth=0, variables=None, subtotal=None, solutions=None):
    if variables is None:
        solutions = []
        subtotal = [0 for _ in xrange(len(coeffs[0]))]
        variables = [0 for _ in xrange(len(coeffs[0])-1)]
    if depth == len(coeffs[0])-2:
        for v in xrange(N-sum(variables[:depth])):
            conditions = all(
                subtotal[i]+coeffs[i][depth]*v > coeffs[i][-1]
                for i in xrange(len(coeffs))
            )
            if conditions:
                variables[depth] = v
                solutions.append(tuple(variables))
    else:
        for v in xrange(N-sum(variables[:depth])):
            variables[depth] = v
            total = [subtotal[i]+coeffs[i][depth]*v for i in xrange(len(coeffs))]
            find_solutions(N, coeffs, depth+1, variables, total, solutions)
    if depth == 0:
        return solutions

要运行此操作,请为每个等式生成系数并将其传递给函数。请记住常量的符号是倒置的!

coeffs = [
    [97, 89, 42, 20, 16, 11, 2, 185],
    [-98, -90, -43, -21, -17, -12, -3, -205]
]
solutions = find_solutions(20, coeffs)
print(len(solutions))

这个在1.6秒内完成 n = 7的情况,在5.8中完成 n = 8的情况。如果您希望 n 变得非常大,我会调查任何可能的优化,但目前它看起来很满意。

剩下的问题是你的方程之和是否总是简化为n1 + n2 + ... nn < N。有一个简单的解决方案,如果情况并非如此,但我选择不过早地将代码过度概括为超出您提供给我们的示例。

最后,我想可以用Java或C#实现相同的方法,它可能更快。如果您的一般情况需要花费更长的时间才能解决,我不介意这样做。

答案 2 :(得分:2)

有1013个解决方案,但我不知道解决此问题的最有效方法。

考虑第二个不等式,17 * n5不能超过205(否则整个左手边不能为正)。这会导致n5 <= 12。通过为每个其他变量计算类似的界限,您可以将问题减少到可以使用嵌套循环快速解决的问题。

此Java代码打印出所有解决方案。

for (int n1 = 0; n1 <= 2; n1++) {
    for (int n2 = 0; n2 <= 2; n2++) {
        for (int n3 = 0; n3 <= 4; n3++) {
            for (int n4 = 0; n4 <= 9; n4++) {
                for (int n5 = 0; n5 <= 12; n5++) {
                    for (int n6 = 0; n6 <= 17; n6++) {
                        for (int n7 = 0; n7 <= 68; n7++) {
                            if (97 * n1 + 89 * n2 + 42 * n3 + 20 * n4 + 16 * n5 + 11 * n6 + 2 * n7 - 185 > 0
                                && -98 * n1 - 90 * n2 - 43 * n3 - 21 * n4 - 17 * n5 - 12 * n6 - 3 * n7 + 205 > 0) {
                                System.out.println(Arrays.asList(n1, n2, n3, n4, n5, n6, n7));
                            }
                        }
                    }
                }
            }
        }
    }
}

通过在

的前几个项之和后立即停止每个循环,可以提高效率。
98n1 + 90n2 + 43n3 + 21n4 + 17n5 + 12n6 + 3n7

达到205。

例如,n4循环可以替换为

for (int n4 = 0; 98 * n1 + 90 * n2 + 43 * n3 + 21 * n4 < 205; n4++)

如果以这种方式更改所有7个循环,则可以非常快速地找到所有1013个解决方案。

答案 3 :(得分:1)

似乎就像使用整数解决方案的线性编程。我认为已经实现了一些算法。 请考虑Linear Programming Tool/Libraries for Java

答案 4 :(得分:1)

(最多13个整数) 这是一个丑陋的暴力与gpgpu(opencl)在1600核心上寻找1013(7英寸)解决方案在9.3毫秒,包括从gpu到CPU内存的阵列下载时间:

修改:修正了n1,n2,n3,因为它们是1,20,400而不是20,20,20有限。

__kernel void solver1(__global __write_only float * subCount){
                        int threadId=get_global_id(0);
                        int ctr=0;
                        int n1=threadId/400;
                        int n2=(threadId/20)%20;
                        int n3=threadId%20;
                        for(int n4=0;n4<=20;n4++) 
                           for(int n5=0;n5<=20;n5++)
                               for(int n6=0;n6<=20;n6++)
                                  for(int n7=0;n7<=20;n7++)
                                      if (
                      (97*n1 + 89*n2 + 42*n3 + 20*n4 + 16*n5 + 11*n6 + 2*n7 - 185 > 0) && 
                      (-98*n1 - 90*n2 - 43*n3 - 21*n4 - 17*n5 - 12*n6 - 3*n7 + 205 > 0))
                                   {ctr++;}
                    subCount[threadId]=ctr;

}

然后像subCount.Sum()这样的东西给出了解决方案的数量。(以微秒为单位)

全局工作量= 8000(线程)(将n4加入到这将使其成为160000并提高性能)

本地工作规模= 160(在我的机器上效率低,使其功率为2是最佳的)

这只是一个要发送到gpu进行编译的字符串。你只需在字符串中添加额外的循环(或只是threadId关系),如n8,n9和alter&#34; if&#34;身体来总结它们。

编辑:为方程式添加1个额外的整数,即使有更多优化,也可将解决方案时间增加到101毫秒(找到4015个解决方案)。

  __kernel void solver2(__global __write_only float * subCount){
                        int threadId=get_global_id(0);
                        int ctr=0;
                        int n1=threadId/160000;     int c1n1=97*n1; int c2n1=-98*n1;
                        int n2=(threadId/8000)%20;  int c1n2=89*n2; int c2n2=- 90*n2 ;
                        int n3=(threadId/400)%20;   int c1n3=42*n3; int c2n3=- 43*n3 ;
                        int n4=(threadId/20)%20;    int c1n4=20*n4 ;int c2n4=- 21*n4 ;
                        int n5=threadId%20;         int c1n5=16*n5 ;int c2n5=- 17*n5 ;
                        int t1=c1n1+c1n2+c1n3+c1n4+c1n5;
                        int t2=c2n1+c2n2+c2n3+c2n4+c2n5;
                               for(int n6=0;n6<=20;n6++)
                                  for(int n7=0;n7<=20;n7++)
                                    for(int n8=0;n8<=20;n8++)
                                        if(t1+ 11*n6 + 2*n7+6*n8 > 185  &&  t2 - 12*n6 - 3*n7-7*n8 > -205)
                                                ctr++;
                        subCount[threadId]=ctr;

                }

工作组大小= 256 全局工作量= 3200000个线程

编辑: 9个整数版本,只有另一个for循环找到14k解决方案,c1 = 3,c2 = -4,1581毫秒。对于n6到n9,将上限从20更改为19,在1310毫秒内得到相同的结果。

编辑:从 Reti43 Molecule 的答案中添加一些,9整数版本 14毫秒(但前5个整数仍然效率低下)。

__kernel void solver3(__global __write_only float * subCount){
                    int threadId=get_global_id(0);
                    int ctr=0;
                    int n1=threadId/160000;     int c1n1=97*n1; int c2n1=-98*n1;
                    int n2=(threadId/8000)%20;  int c1n2=89*n2; int c2n2=- 90*n2 ;
                    int n3=(threadId/400)%20;   int c1n3=42*n3; int c2n3=- 43*n3 ;
                    int n4=(threadId/20)%20;    int c1n4=20*n4 ;int c2n4=- 21*n4 ;
                    int n5=threadId%20;         int c1n5=16*n5 ;int c2n5=- 17*n5 ;
                    int t1=c1n1+c1n2+c1n3+c1n4+c1n5;
                    int t2=c2n1+c2n2+c2n3+c2n4+c2n5;
                    int m=max( max( max( max(n1,n2),n3),n4),n5);
                           for(int n6=0;n6<=20-m;n6++)
                              for(int n7=0;n7<=20-m-n6;n7++)
                                for(int n8=0;n8<=20-m-n6-n7;n8++)
                                    for(int n9=0;n9<=20-m-n6-n7-n8;n9++)
                                        if(t1+ 11*n6 + 2*n7+6*n8 +3*n9> 185  &&  t2 - 12*n6 - 3*n7-7*n8-4*n9 > -205)
                                            ctr++;
                    subCount[threadId]=ctr;

            }
  • 10整数:46k溶液,35ms(c1 = 7,c2 = -8)
  • 11 ints:140k解,103ms(c1 = 1,c2 = -2)
  • 12 ints:383k解决方案,274ms(c1 = 5,c2 = -6)
  • 由于硬件限制,
  • 在15英寸时可能太慢。甚至不会编译20个整数。

    请注意: m = max(max(max(max(n1,n2),n3),n4),n5)不是最优的,但m = n1 + n2 + n3 + n4 + n5是。

我将尝试使用蒙特卡罗方法来克服硬件资源瓶颈。也许会以某种方式收敛到解决方案编号。

答案 5 :(得分:1)

伪代码:

for each inequation:
    find all real roots of the equivalent equation, i.e. the zero-crossings
    for each interval between two adjacent roots:
        pick any number strictly inside the interval
        evaluate the polynomial in that point
            if the evaluated polimonial is positive:
                add every integer in the interval to the list of solutions to that inequation
                (treat the open-ended intervals outside the extreme roots as special cases, they may contain infinite solutions)
find the integers that are in all the lists of solutions to the individual equations