Python 如何进一步优化“熄灯”变体的解算器?

Python 如何进一步优化“熄灯”变体的解算器?,python,performance,Python,Performance,我仍然在解决这个问题,来自于当前的谷歌Foobar挑战。这是熄灯游戏的一种变体,按下一盏灯将翻转同一行和同一列上每盏灯的状态 我以前试过,结果发现对于n>6来说太慢了,而我需要处理2

我仍然在解决这个问题,来自于当前的谷歌Foobar挑战。这是熄灯游戏的一种变体,按下一盏灯将翻转同一行和同一列上每盏灯的状态

我以前试过,结果发现对于n>6来说太慢了,而我需要处理2 我使用@Aryabhata所概述的方法来找到某个系统Ax=b的特殊解决方案x',该解决方案可以与此问题的实例关联,有关详细信息,请参见

找到a的零空间a后,我计算x'的所有和加上基向量的线性组合

这些和的集合是原始问题的所有解的集合,因此我可以用蛮力找到达到最小值的解

应该注意的是,对于n偶数,零空间是空的,A是可逆的,因此x'达到最小值,因为它是唯一的解决方案。如果n为奇数,则零空间基中的向量数为2n-2,因此搜索空间的大小为2^2n-2,在最坏的情况下为2^28 n=15

以下是我的节目:

from itertools import product


MEMO = {}


def bits(iterable):
    bit = 1
    res = 0
    for elem in iterable:
        if elem:
            res |= bit
        bit <<= 1
    return res


def mask(current, n):
    if (current, n) in MEMO:
        return MEMO[(current, n)]

    result = 0
    if current < n:
        for j in xrange(n):
            result += (2 ** ((current - 1)*n + j) + 2 ** (current*n + j))
    else:
        for i in xrange(n):
            result += (2 ** (i*n + current - n) + 2 ** (i*n + current - n + 1))

    MEMO[(current, n)] = result

    return result


# See: https://math.stackexchange.com/a/441697/4471
def check(matrix, n):
    parities = [sum(row) % 2 for row in matrix]
    for i in xrange(n):
        parities.append(sum([row[i] for row in matrix]) % 2)

    return len(set(parities)) == 1


def minimize(matrix, current, n):
    if current == 0:
        # See: https://stackoverflow.com/a/9831671/374865
        return bin(matrix).count("1")
    else:
        return min(minimize(matrix ^ mask(current, n), current - 1, n),
                   minimize(matrix, current - 1, n))


def solve(matrix, n):
    result = [0 for i in xrange(n) for j in xrange(n)]

    for i, j in product(xrange(n), repeat=2):
        if matrix[i][j]:
            for k in xrange(n):
                result[i*n + k] ^= 1
                result[k*n + j] ^= 1
            result[i*n + j] ^= 1

    if n % 2 == 0:
        return sum(result)
    else:
        return minimize(bits(result), 2*n - 2, n)


def answer(matrix):
    n = len(matrix)

    if n % 2 == 0:
        return solve(matrix, n)
    else:
        if check(matrix, n):
            return solve(matrix, n)
        else:
            return -1
编辑:我通过了这一轮。这个实现正确地解决了5个测试用例中的4个,然后我强制执行了第五个。我仍然对进一步的优化或不同的算法感兴趣


编辑2:,并特别给出一个证明,证明这个问题是NP难的第3节,这暗示我们不应该寻找多项式算法。所以问题变成了:我们能得到的最佳指数是多少?

所以我认为你根本不需要对这种奇怪的情况施加暴力。我的线性不是很强,但在R^n中,如果你想找到满足Ax=b的最短x,这基本上就是我们正在做的,在找到一些特殊的解x'后,你可以投影到A的零空间,从x'中减去投影。我相信这种方法即使在F2中也应该有效,尽管我不确定;如果我错了,请纠正我。

所以我认为你根本不需要强迫这个奇怪的案子。我的线性不是很强,但在R^n中,如果你想找到满足Ax=b的最短x,这基本上就是我们正在做的,在找到一些特殊的解x'后,你可以投影到A的零空间,从x'中减去投影。我相信这种方法即使在F2中也应该有效,尽管我不确定;如果我错了,请纠正我。

我尝试了关于线性代数的一切,因为它是GF2,我认为我找不到多项式解。由于最大数量为15,我进一步优化了它,使其大约为2^15

偶数 所以,对于n是偶数,有一种比标准线性代数更快的方法。如果你有这样的例子, 0000 0100 0000 0000 一种解决方案是将点的行和列翻转n次 0100 1111 0100 0100 仔细想想,如果有一个点要翻转,可以将行和列的每个点翻转一次。如果这是有意义的,那么很容易找到一个特定的解决方案

如果我有这样的东西 0100 0010 0010 0000 一个解决办法是 1131 1221 1221 0120 由于翻转两次没有什么区别,所以解决方案可以简化为 1111 1001 1001 0100

然后是奇数 如果n是奇数,我只能想到搜索。但是,我们可以扩展n->n+1,这样问题的解决方案就不应该包含最后一行和最后一列的翻转点

如果你有3x3的东西,比如: 010 001 001 您始终可以尝试将解决方案扩展到以下内容: 010x 001x 001x xxxx 首先,您将确定3乘3中的所有点, 1111 1001 + ? 1001 0100 哪里应该是解决问题的办法 000倍 000倍 000倍 xxxx 如您所见,无论如何翻转,除非xxx是相同的位,否则您无法满足。您可以尝试底部的所有组合进行翻转,然后您可以通过确定翻转是否导致行的最小数目1来确定右侧翻转


我真的不擅长解释,希望它足够清楚。

我尝试了关于线性代数的一切,因为它是GF2,我认为我找不到多项式解。由于最大数量为15,我进一步优化了它,使其大约为2^15

偶数 所以,对于n是偶数,有一种比标准线性代数更快的方法。如果你有这样的例子, 0000 0100 0000 0000 一种解决方案是将点的行和列翻转n次 0100 1111 0100 0100 仔细想想,如果有一个点要翻转,可以将行和列的每个点翻转一次。如果这是有意义的,那么很容易找到一个特定的解决方案

如果我有这样的东西 0100 0010 0010 0000 一个解决办法是 1131 1221 1221 0120 自从翻了两次之后 没有区别,解决方案可以简化为 1111 1001 1001 0100

然后是奇数 如果n是奇数,我只能想到搜索。但是,我们可以扩展n->n+1,这样问题的解决方案就不应该包含最后一行和最后一列的翻转点

如果你有3x3的东西,比如: 010 001 001 您始终可以尝试将解决方案扩展到以下内容: 010x 001x 001x xxxx 首先,您将确定3乘3中的所有点, 1111 1001 + ? 1001 0100 哪里应该是解决问题的办法 000倍 000倍 000倍 xxxx 如您所见,无论如何翻转,除非xxx是相同的位,否则您无法满足。您可以尝试底部的所有组合进行翻转,然后您可以通过确定翻转是否导致行的最小数目1来确定右侧翻转


我真的不擅长解释,希望它足够清楚。

我想重复达尔文的回答非常有用!然而,我花了很长时间才弄明白,即使是在读了好几遍这个答案之后

所以,如果你和我一样,在foobar上迟到了,但想在不使用Java的情况下完成这一步,这里有一个提示可能会有所帮助

下面的光照模式是无法解决的,我想这是让我困惑的

010
001
001
这里有一个非常重要的例子来说明达尔文的想法: 假设你想解这个N=5

11010
01000
11100
10011
00010
我们知道这是可解的,因为所有和和和列的奇偶性都是奇数

如果N为偶数,则更容易找到答案。 因此,展开到N=6,如下所示:

110100
010000
111000
100110
000100
000000
正如达尔文所说,我们想要一个解决方案,它不会触及最下面一行或最右边一列中的任何灯光。然后我们可以采用这个解决方案,忽略底部的行和右边的列,我们就得到了原始N=5问题的解决方案。因此,我们希望在答案中插入值,而不仅仅是零,而且不要在这些列中按下任何按钮

这意味着不能在右下角放1。右下角的指示灯表示在最下面一行或最右边的列中至少按下一个按钮。因此,一个自由度消失了。接下来,要使最下面一行没有按钮按下,所有这些灯都必须有一个均匀的奇偶校验。查看偶数N案例的答案以了解原因。因此,在上述情况下,奇偶性是奇数。我们可以填充最下面一行,但必须使用奇数1。这就剥夺了另一个自由度。如果我们插入4个值1s或0s,则第5个值由该奇偶校验要求确定。这里有N-1个自由度

这就是暴力部分的用武之地。我必须在这里尝试所有可能的值,在这种情况下,所有5位奇偶校验集 一个例子是插入10101

110100
010000
111000
100110
000100
10101_
现在我们可以使用偶数N的规则得到一个解。 我会写下每一点的行和列的实际总和,即使只是需要奇偶校验,以便更清楚地说明我做了什么

65555o      01111o
53343o      11101o
65465o   -> 01001o
66554o      00110o
54333o      10111o
66464_      00000_
我把小o放在最右边,说平价是奇怪的,因为我们还没有做任何事情。因为奇偶性是奇怪的,这是不好的,我们将有一个解决方案,所有这些都被触及。但是它们都有奇偶校验,所以我们只需要插入一些值,使得最右边的列的奇偶校验是奇偶校验,所以每个点的奇偶校验是偶数,如果这有意义的话

这是darwinsenior在上面的评论中所说的,但我很难遵循唯一的要求是列的奇偶校验是奇数,因此在解决方案中不需要按下最右边的按钮。 我们不需要强行这样做,我们可以使用一些逻辑来确定在保持奇偶性要求的同时按下哪些按钮。顺便说一下,我们这里有N-1个自由变量,所以2*N-1个自由变量,就像前面提到的其他解决方案一样。在这里,我们可以看到我们的选择对按钮按下次数的影响。我将为列选择以下值:11001 现在的例子是:

110101                                    X00000 
010001                                    000X00 
111000   -- again use even N solution ->  0X00X0 
100110                                    00XX00 
000101                                    0X0000 
10101_                                    000000 
所以,我认为这给了我们一个原始N=5情况的答案,只需去掉底部和右侧的零。它有7个按钮,我想这是我们能做的最好的一个,但我不确定


还有一件事——即使需要强制执行的案例数量大幅减少,我仍然必须按照尤金说的去做,并使用整数列表,而不是整数列表。看看雅格布的代码和bits函数。有趣的东西。

我想重申达尔文的答案非常有用!然而,我花了很长时间才弄明白,即使是在读了好几遍这个答案之后

所以,如果你和我一样,在foobar上迟到了,但想在不使用Java的情况下完成这一步,这里有一个提示可能会有所帮助

下面的光照模式是无法解决的,我想这是让我困惑的

010
001
001
这里有一个非常重要的例子来说明达尔文的想法: 假设你想解这个N=5

11010
01000
11100
10011
00010
我们知道这是可解的,因为所有和和和列的奇偶性都是奇数

及 如果N为偶数,则更容易找到答案。 因此,展开到N=6,如下所示:

110100
010000
111000
100110
000100
000000
正如达尔文所说,我们想要一个解决方案,它不会触及最下面一行或最右边一列中的任何灯光。然后我们可以采用这个解决方案,忽略底部的行和右边的列,我们就得到了原始N=5问题的解决方案。因此,我们希望在答案中插入值,而不仅仅是零,而且不要在这些列中按下任何按钮

这意味着不能在右下角放1。右下角的指示灯表示在最下面一行或最右边的列中至少按下一个按钮。因此,一个自由度消失了。接下来,要使最下面一行没有按钮按下,所有这些灯都必须有一个均匀的奇偶校验。查看偶数N案例的答案以了解原因。因此,在上述情况下,奇偶性是奇数。我们可以填充最下面一行,但必须使用奇数1。这就剥夺了另一个自由度。如果我们插入4个值1s或0s,则第5个值由该奇偶校验要求确定。这里有N-1个自由度

这就是暴力部分的用武之地。我必须在这里尝试所有可能的值,在这种情况下,所有5位奇偶校验集 一个例子是插入10101

110100
010000
111000
100110
000100
10101_
现在我们可以使用偶数N的规则得到一个解。 我会写下每一点的行和列的实际总和,即使只是需要奇偶校验,以便更清楚地说明我做了什么

65555o      01111o
53343o      11101o
65465o   -> 01001o
66554o      00110o
54333o      10111o
66464_      00000_
我把小o放在最右边,说平价是奇怪的,因为我们还没有做任何事情。因为奇偶性是奇怪的,这是不好的,我们将有一个解决方案,所有这些都被触及。但是它们都有奇偶校验,所以我们只需要插入一些值,使得最右边的列的奇偶校验是奇偶校验,所以每个点的奇偶校验是偶数,如果这有意义的话

这是darwinsenior在上面的评论中所说的,但我很难遵循唯一的要求是列的奇偶校验是奇数,因此在解决方案中不需要按下最右边的按钮。 我们不需要强行这样做,我们可以使用一些逻辑来确定在保持奇偶性要求的同时按下哪些按钮。顺便说一下,我们这里有N-1个自由变量,所以2*N-1个自由变量,就像前面提到的其他解决方案一样。在这里,我们可以看到我们的选择对按钮按下次数的影响。我将为列选择以下值:11001 现在的例子是:

110101                                    X00000 
010001                                    000X00 
111000   -- again use even N solution ->  0X00X0 
100110                                    00XX00 
000101                                    0X0000 
10101_                                    000000 
所以,我认为这给了我们一个原始N=5情况的答案,只需去掉底部和右侧的零。它有7个按钮,我想这是我们能做的最好的一个,但我不确定



还有一件事——即使需要强制执行的案例数量大幅减少,我仍然必须按照尤金说的去做,并使用整数列表,而不是整数列表。看看雅格布的代码和bits函数。有趣的东西。

你可以试着用Pypit运行它,但这不是一个真正的选项;这段代码需要在Google Foobar的解释器上运行,这个解释器对我来说是未知和不可访问的。ping@Sven,因为我认为他正在/正在处理同一个问题。你是如何强行执行2^28的事情的?我没有。有一个能够正确解决5个测试用例中的4个,并且解决方案空间只有225个整数的实现,我强迫它,因为我尝试了所有可能的解决方案;这段代码需要在Google Foobar的解释器上运行,这个解释器对我来说是未知和不可访问的。ping@Sven,因为我认为他正在/正在处理同一个问题。你是如何强行执行2^28的事情的?我没有。有一个能正确解决5个测试用例中4个的实现,以及一个只有225个整数的解决方案空间,我强迫它,因为我尝试了所有可能的解决方案。这是一个有趣的想法,但我不理解一个细节:基向量自身的点积在F_2中似乎为0,这使得投影公式毫无意义。为什么基向量的点积本身为0?也许我误解了什么,但下面的不是真的吗@我想你会遇到的问题是,解向量与零空间中的向量正交,所以你的投影长度为零。这也可能发生。我记得,当我学习线性代数时,每个定理的前缀都是这样的:让F是一个特征域,而不是2…:这是一个有趣的想法,但我不了解一个细节:基向量与自身的点积在F_2中似乎为0,这使得投影公式毫无意义。为什么基向量与自身的点积为0?也许我误解了什么,但下面的说法不是真的吗@我想你会遇到的问题是,解向量与零空间中的向量正交,所以你的投影长度为零。这也可能发生。我记得,虽然
我在学习线性代数,每一个定理的前缀都是,让F是一个特征域,而不是2…:我想我理解了它的要点,但我不明白这如何将问题简化为在大小为n的空间中搜索:xs的数量实际上是2n+1,比2n-2稍差。我恐怕我的英语不好。我解释得更清楚。如果你已经翻转了底线,你不必一个接一个地尝试右手边,因为每一行都是独立的。如果你有一条像11101这样的线,你肯定会翻转它,因为最后你只需要翻转1而不是4。相反,如果你有像00110这样的东西,你不必翻转它。这够清楚吗?顺便说一句,这是我在foobar挑战中使用Java而不是python的唯一问题,它在我的计算机上运行得非常快,但它总是告诉我时间已过。我可以确认@darwinsenior所描述的解决方案有效,得到了非常简洁的答案,并且足够快,可以通过挑战。更准确地说,在代码中使用名为result的切换矩阵的列表速度太慢,但将其更改为int列表是可行的。我想我理解了它的要点,但我看不出这如何将问题简化为在大小为n的空间中搜索:x的数量实际上是2n+1,比2n-2差一点。恐怕我的英语不好。我解释得更清楚。如果你已经翻转了底线,你不必一个接一个地尝试右手边,因为每一行都是独立的。如果你有一条像11101这样的线,你肯定会翻转它,因为最后你只需要翻转1而不是4。相反,如果你有像00110这样的东西,你不必翻转它。这够清楚吗?顺便说一句,这是我在foobar挑战中使用Java而不是python的唯一问题,它在我的计算机上运行得非常快,但它总是告诉我时间已过。我可以确认@darwinsenior所描述的解决方案有效,得到了非常简洁的答案,并且足够快,可以通过挑战。更准确地说,在代码中使用名为result的切换矩阵列表速度太慢,但将其更改为int列表是可行的。