数学&算法
首发于数学&算法

点灯游戏的O(n^3)算法

点灯游戏简介

一层大楼共有n\times n个房间,每个房间都有一盏灯和一个按钮。按动一个房间的按钮后,这个房间和周围四个相邻的房间的灯的状态全部都会改变(由暗变为亮或者亮变为暗)。目标是通过按按钮把所有的灯都点亮(默认情况下全暗)。

点灯游戏规律

我们不难发现以下规律

1. 按偶数次按钮相当于没有按。

2. 无论按按钮顺序如何结果总是一样的。

因此我们有以下结论

1. 对于盘面上的每一个按钮,我们只需要考虑其按开或关的状态。

2. 每一个按钮的状态都是互相独立的,不需要考虑按按钮的顺序。

点灯游戏算法概览

1. 完全穷举法, O(2^{n^2})

2. 首行穷举法, O(2^n)

3. 完全方程法, O(n^6)

4. 首行方程法, O(n^3)

点灯游戏算法详解

1.完全穷举法, O(2^{n^2})

对于每一个按钮只有开和关两种状态。而一旦所有按钮的状态都确定了,灯的状态也就确定了。因此,我们只需要把所有按钮的所有可能的状态列举出来,算出对应灯的状态并判断所有灯是否都点亮了即可。

不难发现,一个按钮的状态是 2 种,因此x个按钮的状态就是 2^x 种。一个 n\times n 的房间一共有 n\times n 个按钮,因此就有 2^{n\times n} 种状态。这种算法的复杂度就是 O(2^{n^2})

举个例子,下面是一种房间按钮的状态和对应灯的状态(■ 代表开,□代表关,●代表亮,○代表暗):

■ □ ■ □ ■ ● ● ● ● ●
□ ■ □ ■ □ ● ● ● ● ●
■ □ □ □ ■ ● ● ○ ● ●
□ ■ □ ■ □ ● ● ● ● ●
■ □ ■ □ ■ ● ● ● ● ●

下面是另一个例子:

■ ■ □ □ □ ○ ○ ○ ○ ○
□ □ ■ □ □ ○ ○ ○ ○ ○
■ □ ■ ■ □ ○ ○ ● ○ ○
■ □ □ □ ■ ○ ○ ○ ○ ○
□ ■ ■ □ ■ ○ ○ ○ ○ ○

这是合并后的结果( 5\times 5 下的解):

□ ■ ■ □ ■ ● ● ● ● ●
□ ■ ■ ■ □ ● ● ● ● ●
□ □ ■ ■ ■ ● ● ● ● ●
■ ■ □ ■ ■ ● ● ● ● ●
■ ■ □ □ □ ● ● ● ● ●

有些情况下,即使按了按钮,全部灯的状态也可能不变:

■ □ ■ □ ■ ○ ○ ○ ○ ○
■ □ ■ □ ■ ○ ○ ○ ○ ○
□ □ □ □ □ ○ ○ ○ ○ ○
■ □ ■ □ ■ ○ ○ ○ ○ ○
■ □ ■ □ ■ ○ ○ ○ ○ ○

因此,解可能不是唯一的(对于解的存在性,唯一性及解的个数,将在算法3中讨论):

■ ■ □ □ □ ● ● ● ● ●
■ ■ □ ■ ■ ● ● ● ● ●
□ □ ■ ■ ■ ● ● ● ● ●
□ ■ ■ ■ □ ● ● ● ● ●
□ ■ ■ □ ■ ● ● ● ● ●

2.首行穷举法, O(2^n)

完全穷举法的时间复杂度太高,当 n=6 时,房间的状态已高达 2^{36}=68719476736 种。在游玩的过程中我们会去尝试点亮尽可能多的灯。很多状态(例如只按 12 个按钮)显然无法满足我们的要求而可以快速排除。

为了使得尽可能多的灯被点亮,假设我们已经决定了第一行按钮的状态,此时只有第一行和第二行的灯被改变了。由于只有第一行或第二行按钮会改变第一行灯的状态,为了使得第一行的灯全亮,由于第一行按钮状态已经确定,我们只能去按第二行的按钮,此时第二行按钮开的状态和第一行灯灭的状态是相反的(为了让第一行灯亮,我们必须去按第二行对应列的按钮),此时第二行的按钮状态也被确定了。

由于第二行按钮状态确定了,为了使第二行灯全亮,第三行按钮的状态也被确定了。以此类推,整个房间的按钮都被第一行按钮的状态确定了,而房间里除了最后一行的灯也都是全亮的。

因此,其实我们不需要把房间里所有的按钮可能的状态全部列举出来,而只需要把第一行按钮可能的状态列举出来就行了。对于每一种状态,我们只需要按照上面的步骤计算出后面 n-1 行的按钮状态,然后计算出最后一行灯的状态就行了。对于房间里可能出现的别的按钮的状态的可能性,由于前面 n-1 行的灯不是全亮所以不需要考虑。

一行里的灯一共有 n 个,因此就有 2^n 种状态。这种算法的复杂度就是 O(2^n)

举个例子,假设第一行按钮的状态是:

■ □ ■ □ □

则按钮和灯的状态为:

■ □ ■ □ □ ● ○ ● ● ○
□ □ □ □ □ ● ○ ● ○ ○
□ □ □ □ □ ○ ○ ○ ○ ○
□ □ □ □ □ ○ ○ ○ ○ ○
□ □ □ □ □ ○ ○ ○ ○ ○

因此,为了让第一行第二列和第五列的灯亮,必须按第二行第二列和第五列的按钮:

■ □ ■ □ □ ● ● ● ● ●
□ ■ □ □ ■ ○ ● ○ ● ●
□ □ □ □ □ ○ ● ○ ○ ●
□ □ □ □ □ ○ ○ ○ ○ ○
□ □ □ □ □ ○ ○ ○ ○ ○

然后是第三行第一列和第三列的按钮:

■ □ ■ □ □ ● ● ● ● ●
□ ■ □ □ ■ ● ● ● ● ●
■ □ ■ □ □ ● ● ● ● ●
□ □ □ □ □ ● ○ ● ○ ○
□ □ □ □ □ ○ ○ ○ ○ ○

由于第三行的灯已经全亮了,第四行的按钮不需要按动(全是关,□)。

第五列的按钮如下和最终灯的结果如下:

■ □ ■ □ □ ● ● ● ● ●
□ ■ □ □ ■ ● ● ● ● ●
■ □ ■ □ □ ● ● ● ● ●
□ □ □ □ □ ● ● ● ● ●
□ ■ □ ■ ■ ● ● ○ ○ ○

由于第五行的灯没有全亮,因此第一行按钮的状态不是想要的结果。

下面举另外一个例子,假设第一行按钮的状态是:

■ ■ □ □ □

则按钮和灯的状态为:

■ ■ □ □ □ ○ ○ ● ○ ○
□ □ □ □ □ ● ● ○ ○ ○
□ □ □ □ □ ○ ○ ○ ○ ○
□ □ □ □ □ ○ ○ ○ ○ ○
□ □ □ □ □ ○ ○ ○ ○ ○

第二行按钮按动后:

■ ■ □ □ □ ● ● ● ● ●
■ ■ □ ■ ■ ● ● ○ ○ ○
□ □ □ □ □ ● ● ○ ● ●
□ □ □ □ □ ○ ○ ○ ○ ○
□ □ □ □ □ ○ ○ ○ ○ ○

第三行按钮按动后:

■ ■ □ □ □ ● ● ● ● ●
■ ■ □ ■ ■ ● ● ● ● ●
□ □ ■ ■ ■ ● ○ ○ ○ ●
□ □ □ □ □ ○ ○ ● ● ●
□ □ □ □ □ ○ ○ ○ ○ ○

第四行按钮按动后:

■ ■ □ □ □ ● ● ● ● ●
■ ■ □ ■ ■ ● ● ● ● ●
□ □ ■ ■ ■ ● ● ● ● ●
□ ■ ■ ■ □ ● ○ ○ ● ○
□ □ □ □ □ ○ ● ● ● ○

最后一行按钮按动后和最终结果:

■ ■ □ □ □ ● ● ● ● ●
■ ■ □ ■ ■ ● ● ● ● ●
□ □ ■ ■ ■ ● ● ● ● ●
□ ■ ■ ■ □ ● ● ● ● ●
□ ■ ■ □ ■ ● ● ● ● ●

所有的灯都被点亮了,因此第一行按钮的状态是我们想要的结果。这与我们算法1中的结果是相同的。

3.完全方程法, O(n^6)

虽然算法2已经将复杂度降到了 O(2^n) ,但是这仍旧是指数时间的复杂度。对于n>30来说,这不是一般计算机所能承受的范围了。有没有更快的算法呢?下面将介绍多项式时间内的算法。

在算法2中我们发现,第n行的灯的状态是由第 n-1 行、第 n 行和第 n+1 行按钮的状态决定的,而不是由所有的按钮决定的。通过这点,我们将算法的复杂度从 O(2^{n^2}) 降到了 O(2^n)。但是对于同一行内按钮以及灯的状态,我们还是将其视为一个整体。那么,对于同一行内按钮或者灯的状态,它们之间是否有关系呢?

答案是肯定的。我们知道,一个按钮可以决定它和它周围4个灯的状态,同样的,一个灯的状态则是由它和它周围4个按钮决定的。

我们假设灯的状态是由其中两个按钮决定的,则具体的情况可以表示为:

□ + □ = ○
■ + □ = ●
□ + ■ = ●
■ + ■ = ○

从数学角度来说,我们这样假设:□=0,■=1,○=0,●=1。于是,上面的式子就变为了:

0 + 0 = 0
1 + 0 = 1
0 + 1 = 1
1 + 1 = 0

这样的+号我们称之为(逻辑上的)异或运算。

由于情况下,灯和它周围开的按钮的数量决定了灯的状态,例如●22(第二行第二列的灯)是由■12、■21、■22、■23和■32决定的,于是我们有:

■12 + ■21 + ■22 + ■23 + ■32 = ●22

例如■12,■22和■23是开,■21和■32是关,于是●22被开了3次。由于开两次等同于没开,所以●22相当于被开了1次,是亮的状态。也可以表示为:

■ + □ + ■ + ■ + □ = ●

于是,对于房间里所有按钮和灯的状态,我们可以列出n*n个式子。这里以3*3举例:

■11 + ■12 + ■21 = ●11
■11 + ■12 + ■13 + ■22 = ●12
■12 + ■13 + ■23 = ●13
■11 + ■21 + ■22 + ■31 = ●21
■12 + ■21 + ■22 + ■23 + ■32 = ●22
■13 + ■22 + ■23 + ■33 = ●23
■21 + ■31 + ■32 = ●31
■22 + ■31 + ■32 + ■33 = ●32
■23 + ■32 + ■33 = ●33

由于我们的目标是将所有的灯点亮,因此所有的灯的状态都是亮(●,1)。由于按钮一共有9个,因此从数学角度来说我们得到了一个九元一次方程组:

x1 + x2 + x4 = 1
x1 + x2 + x3 + x5 = 1
x2 + x3 + x6 = 1
x1 + x4 + x5 + x7 = 1
x2 + x4 + x5 + x6 + x8 = 1
x3 + x5 + x6 + x9 = 1
x4 + x7 + x8 = 1
x5 + x7 + x8 + x9 = 1
x6 + x8 + x9 = 1

需要注意的是,这里的+是我们刚才所说的异或运算。解这个方程组,我们就能得到所有按钮的状态。上面这个方程组的解是:

x1 = 1, x2 = 0, x3 = 1,
x4 = 0, x5 = 1, x6 = 0,
x7 = 1, x8 = 0, x9 = 1.

于是,所有按钮的状态是:

■ □ ■
□ ■ □
■ □ ■

也就是 3\times 3 房间的答案。

为了解出上面的方程,我们可以将方程式组表示成矩阵的形式,然后对矩阵进行高斯消元:

■ ■ □ ■ □ □ □ □ □ ● ○ ○ ○ ○ ○ ○ ○ ○
■ ■ ■ □ ■ □ □ □ □ ○ ● ○ ○ ○ ○ ○ ○ ○
□ ■ ■ □ □ ■ □ □ □ ○ ○ ● ○ ○ ○ ○ ○ ○
■ □ □ ■ ■ □ ■ □ □ ○ ○ ○ ● ○ ○ ○ ○ ○
□ ■ □ ■ ■ ■ □ ■ □ ○ ○ ○ ○ ● ○ ○ ○ ○
□ □ ■ □ ■ ■ □ □ ■ ○ ○ ○ ○ ○ ● ○ ○ ○
□ □ □ ■ □ □ ■ ■ □ ○ ○ ○ ○ ○ ○ ● ○ ○
□ □ □ □ ■ □ ■ ■ ■ ○ ○ ○ ○ ○ ○ ○ ● ○
□ □ □ □ □ ■ □ ■ ■ ○ ○ ○ ○ ○ ○ ○ ○ ●

高斯消元的过程等同于求逆矩阵,因此:

● ○ ○ ○ ○ ○ ○ ○ ○ ■ □ ■ □ □ ■ ■ ■ □
○ ● ○ ○ ○ ○ ○ ○ ○ □ □ □ □ ■ □ ■ ■ ■
○ ○ ● ○ ○ ○ ○ ○ ○ ■ □ ■ ■ □ □ □ ■ ■
○ ○ ○ ● ○ ○ ○ ○ ○ □ □ ■ □ ■ ■ □ □ ■
○ ○ ○ ○ ● ○ ○ ○ ○ □ ■ □ ■ ■ ■ □ ■ □
○ ○ ○ ○ ○ ● ○ ○ ○ ■ □ □ ■ ■ □ ■ □ □
○ ○ ○ ○ ○ ○ ● ○ ○ ■ ■ □ □ □ ■ ■ □ ■
○ ○ ○ ○ ○ ○ ○ ● ○ ■ ■ ■ □ ■ □ □ □ □
○ ○ ○ ○ ○ ○ ○ ○ ● □ ■ ■ ■ □ □ ■ □ ■

这个矩阵的每一行表示,如果需要单独点亮一个灯,需要按哪些按钮。因此,点亮所有灯就相当于将所有行加起来:

● ● ● ● ● ● ● ● ● ■ □ ■ □ ■ □ ■ □ ■

也就是:

■ □ ■
□ ■ □
■ □ ■

对于 5\times 5 的房间,我们有:

■ ■ □ □ □ ■ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○
■ ■ ■ □ □ □ ■ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ ○ ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○
□ ■ ■ ■ □ □ □ ■ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ ○ ○ ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○
□ □ ■ ■ ■ □ □ □ ■ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ ○ ○ ○ ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○
□ □ □ ■ ■ □ □ □ □ ■ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ ○ ○ ○ ○ ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○
■ □ □ □ □ ■ ■ □ □ □ ■ □ □ □ □ □ □ □ □ □ □ □ □ □ □ ○ ○ ○ ○ ○ ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○
□ ■ □ □ □ ■ ■ ■ □ □ □ ■ □ □ □ □ □ □ □ □ □ □ □ □ □ ○ ○ ○ ○ ○ ○ ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○
□ □ ■ □ □ □ ■ ■ ■ □ □ □ ■ □ □ □ □ □ □ □ □ □ □ □ □ ○ ○ ○ ○ ○ ○ ○ ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○
□ □ □ ■ □ □ □ ■ ■ ■ □ □ □ ■ □ □ □ □ □ □ □ □ □ □ □ ○ ○ ○ ○ ○ ○ ○ ○ ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○
□ □ □ □ ■ □ □ □ ■ ■ □ □ □ □ ■ □ □ □ □ □ □ □ □ □ □ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○
□ □ □ □ □ ■ □ □ □ □ ■ ■ □ □ □ ■ □ □ □ □ □ □ □ □ □ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○
□ □ □ □ □ □ ■ □ □ □ ■ ■ ■ □ □ □ ■ □ □ □ □ □ □ □ □ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○
□ □ □ □ □ □ □ ■ □ □ □ ■ ■ ■ □ □ □ ■ □ □ □ □ □ □ □ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○
□ □ □ □ □ □ □ □ ■ □ □ □ ■ ■ ■ □ □ □ ■ □ □ □ □ □ □ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○
□ □ □ □ □ □ □ □ □ ■ □ □ □ ■ ■ □ □ □ □ ■ □ □ □ □ □ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○
□ □ □ □ □ □ □ □ □ □ ■ □ □ □ □ ■ ■ □ □ □ ■ □ □ □ □ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ○ ○ ○ ○ ○ ○ ○ ○ ○
□ □ □ □ □ □ □ □ □ □ □ ■ □ □ □ ■ ■ ■ □ □ □ ■ □ □ □ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ○ ○ ○ ○ ○ ○ ○ ○
□ □ □ □ □ □ □ □ □ □ □ □ ■ □ □ □ ■ ■ ■ □ □ □ ■ □ □ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ○ ○ ○ ○ ○ ○ ○
□ □ □ □ □ □ □ □ □ □ □ □ □ ■ □ □ □ ■ ■ ■ □ □ □ ■ □ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ○ ○ ○ ○ ○ ○
□ □ □ □ □ □ □ □ □ □ □ □ □ □ ■ □ □ □ ■ ■ □ □ □ □ ■ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ○ ○ ○ ○ ○
□ □ □ □ □ □ □ □ □ □ □ □ □ □ □ ■ □ □ □ □ ■ ■ □ □ □ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ○ ○ ○ ○
□ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ ■ □ □ □ ■ ■ ■ □ □ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ○ ○ ○
□ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ ■ □ □ □ ■ ■ ■ □ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ○ ○
□ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ ■ □ □ □ ■ ■ ■ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ○
□ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ ■ □ □ □ ■ ■ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ●

解得:

● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● □ ■ ■ ■ □ □ □ ■ □ ■ □ □ □ ■ ■ □ □ □ □ ■ □ □ □ □ □
○ ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ○ ■ ■ □ ■ ■ □ ■ □ □ □ □ □ ■ ■ ■ □ □ □ ■ □ □ □ □ □ □
○ ○ ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ● ■ □ ■ ■ ■ ■ □ ■ ■ □ □ □ ■ ■ □ ■ ■ ■ ■ ■ □ ■ □ □ □
○ ○ ○ ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ○ ■ ■ ■ □ □ □ ■ □ □ □ □ □ □ □ □ □ ■ □ □ □ ■ ■ ■ □ □
○ ○ ○ ○ ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● □ ■ ■ □ ■ ■ □ □ □ □ ■ □ ■ □ ■ □ □ ■ □ ■ ■ ■ □ □ □
○ ○ ○ ○ ○ ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ● □ □ ■ □ ■ □ ■ ■ □ ■ □ □ ■ □ □ □ □ □ ■ ■ □ □ □ □ □
○ ○ ○ ○ ○ ○ ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ □ ■ □ ■ □ ■ ■ □ ■ ■ □ □ □ ■ □ ■ ■ ■ □ □ □ ■ □ □ □
○ ○ ○ ○ ○ ○ ○ ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ● ■ □ ■ □ □ ■ □ ■ ■ □ □ □ □ □ ■ ■ □ ■ □ ■ ■ □ ■ □ □
○ ○ ○ ○ ○ ○ ○ ○ ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ □ □ ■ □ □ □ ■ ■ ■ □ ■ □ □ ■ ■ ■ □ □ ■ □ □ ■ ■ □ □
○ ○ ○ ○ ○ ○ ○ ○ ○ ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ● ■ □ □ □ □ ■ ■ □ □ □ ■ □ ■ □ ■ □ ■ ■ □ ■ □ □ ■ □ □
○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ○ □ □ □ □ ■ □ □ □ ■ ■ □ □ ■ □ ■ ■ ■ ■ ■ □ □ ■ □ □ □
○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ○ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ ■ □ □ □ ■ ■ ■ □ □
○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ □ ■ ■ □ ■ ■ □ □ □ ■ ■ □ ■ ■ □ □ □ ■ □ □ ■ ■ □ □ □
○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ○ ■ ■ ■ □ □ □ ■ □ ■ □ □ □ ■ ■ ■ □ □ □ ■ □ □ □ □ □ □
○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ○ ○ ○ ○ ○ ○ ○ ○ ● ○ ■ ■ □ □ ■ □ □ ■ ■ ■ ■ □ □ ■ ■ ■ ■ □ ■ □ ■ □ □ □ □
○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ○ ○ ○ ○ ○ ○ ○ ● ● □ □ ■ □ □ □ ■ ■ ■ □ ■ □ □ □ ■ ■ □ ■ □ ■ ■ □ ■ □ □
○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ○ ○ ○ ○ ○ ○ ○ ○ □ □ ■ ■ □ □ ■ □ □ ■ ■ ■ □ □ ■ □ ■ ■ ■ □ □ □ ■ □ □
○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ○ ○ ○ ○ ○ ● ● □ □ ■ □ ■ □ ■ ■ □ ■ ■ □ ■ □ □ ■ ■ □ ■ ■ ■ □ □ □ □
○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ○ ○ ○ ○ ○ ○ □ ■ ■ □ □ ■ □ □ ■ □ ■ □ □ ■ ■ □ ■ ■ ■ □ □ □ ■ □ □
○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ○ ○ ○ ● ● ■ □ ■ □ ■ ■ □ ■ □ ■ □ □ □ □ □ ■ □ ■ □ ■ ■ □ ■ □ □
○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ○ ○ ○ ● □ □ □ ■ ■ □ □ ■ □ □ □ ■ ■ □ ■ ■ □ ■ □ ■ ■ ■ □ □ □
○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ○ ● ○ □ □ ■ ■ ■ □ ■ □ ■ □ ■ ■ ■ □ □ □ □ □ □ □ ■ ■ ■ □ □
○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ● ● □ □ □ ■ □ □ □ ■ ■ ■ □ ■ □ □ □ ■ ■ □ ■ ■ □ ■ □ □ □
○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ □ ■ ■ ■ □ ■ □ ■ □ ■ ■ ■ □ ■ ■ ■ □ ■ □ ■ □ ■ ■ ■ □
○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ■ □ ■ □ ■ ■ □ ■ □ ■ □ □ □ □ □ ■ □ ■ □ ■ ■ □ ■ □ ■

这次我们发现,左边的灯矩阵并不是每行都是只有一个亮的,最后两行是全灭的。因此,右边最后两行按钮按的结果仍旧是全灭,也是算法1中的一种情况。当然,这两行的组合也是全灭。

只有当灯矩阵的一行只有一个灯亮时(一行只有一个●,这里有5行是这种情况),这个灯才能被单独的打开。对于其余的灯来说(这里有18行是这种情况),无论怎么按按钮都会影响到其它的灯(这里是会影响到最后两个灯)。

虽然灯矩阵的每一行并不是只有一个灯,但是将每一列相加的结果仍旧是1(亮)。因此,我们仍然可以通过将所有行相加的方法得到灯全亮时按钮的状态:

● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ■ □ ■ ■ □ □ ■ ■ ■ □ ■ ■ ■ □ □ ■ ■ □ ■ ■ □ □ □ ■ ■

也就是:

■ □ ■ ■ □
□ ■ ■ ■ □
■ ■ ■ □ □
■ ■ □ ■ ■
□ □ □ ■ ■

对于 23 个灯来说,这样的解是唯一的。然而,由于有能让灯全灭的按钮状态的存在(两种),因此将此解和全灭状态进行组合仍旧是解。因此,这种情况下一共有 1\times 2^2=4 种解。

从矩阵的角度来说, 5\times 5 房间矩阵的秩为 23 ,因此这种情况解的个数就是 2^{5\times 5-23}=4。可以证明,对于 n\times n 的矩阵来说,只要灯矩阵是单位矩阵(房间的初始状态为灯全灭,目标灯全亮),那么解一定存在。事实上,存在这样的定理:

如果游戏是对称的(如果按下按钮1后会改变灯2,那么按下按钮2会改变灯1),那么游戏是可解的,当且仅当灯全灭的按钮状态组是偶数个。这是由于游戏是对称的,因此灯全灭状态的按钮总是会使得所有灯被按偶数次。一个可解状态灯一定被全灭状态按钮按了偶数次。

对于点灯游戏来说,显然所有的按钮矩阵都是对称的,因此灯全灭的按钮状态组一定是偶数个,因此游戏一定是可解的。

关于用以计算出解的多余的矩阵秩的数目,可以参考这个数列:

A159257 - OEIS​oeis.orgoeis.org

由于高斯消元或矩阵求逆法的时间复杂度是 O(n^3) ,而我们矩阵的边长是 n\times n ,因此该算法的时间复杂度是 O(n^6)

4.首行方程法, O(n^3)

上述的算法将时间复杂度降到了多项式时间 O(n^6) ,对于 n<=100 的房间是没有问题了。但是对于 n>1000 乃至 n>10000 的房间却也无能为力了。有没有办法进一步优化算法呢?答案是肯定的,下面就是见证奇迹的时刻了。

在算法1中,我们把全部的按钮视为整体,取得了全部灯的状态。在算法2中,我们将行视为整体,将时间复杂度减少到了O(2^n)。而在算法3中,我们仍旧将按钮视为整体(在方程中,我们并没有区分每行每列,而是将所有按钮和灯分别编号)。由于第一行的按钮的状态可以决定后面所有行按钮的状态,那么我们是否能将第一行按钮看作一个整体,并将其应用到我们的方程中呢?这就带来了我们的算法4。

还是以 3\times 3 的房间为例,我们假设第一行的按钮为■11,■12,■13,那么按照算法2,我们可以将●11,●12,●13表示为:

●11 = ■11 + ■12
●12 = ■11 + ■12 + ■13
●13 = ■12 + ■13

由于对于第二行的按钮,灯暗的时候按钮应该开,灯亮的时候按钮应该关,因此按钮的状态和灯是相反的,于是我们有(这里的-代表否运算):

■21 = -●11 = -(■11 + ■12)
■22 = -●12 = -(■11 + ■12 + ■13)
■23 = -●13 = -(■12 + ■13)

紧接着,我们可以计算出第二行灯的状态:

●21 = ■11 + ■21 + ■22 = ■11 + -(■11 - ■12) + -(■11 - ■12 - ■13) = ■11 + ■13
●22 = ■12 + ■21 + ■22 + ■23 = ■12 + -(■11 + ■12) + -(■11 + ■12 + ■13) + -(■12 + ■13) = ●
●23 = ■13 + ■22 + ■23 = ■13 + -(■11 + ■12 + ■13) + -(■12 + ■13) = ■13 + ■11

以及第三行按钮的状态:

■31 = -●21 = -(■13 + ■11)
■32 = -●22 = □
■33 = -●23 = -(■11 + ■13)

最后一行灯的状态:

●31 = ■21 + ■31 + ■32 = -(■11 + ■12) + -(■13 + ■11) + □ = ■12 + ■13
●32 = ■22 + ■31 + ■32 + ■33 = -(■11 + ■12 + ■13) + (■13 + ■11) + 0 + (■11 + ■13) = -(■11 + ■12 + ■13)
●33 = ■23 + ■32 + ■33 = -(■12 + ■13) + □ + -(■11 + ■13) = ■12 + ■11

也就是方程:

X2 + x3 = 1
x1 + x2 + x3 = 0
x2 + x1 = 1

解得:

x1 = 1, x2 = 0, x3 = 1

因此 3\times 3 房间的按钮第一行为:

■ □ ■

由此推算出全部按钮为:

■ □ ■
□ ■ □
■ □ ■

用矩阵方法表示时过程是这样的,先是第一行按钮:

■11	■ □ □ ○	■12	□ ■ □ ○	■13	□ □ ■ ○

接着是第一行灯(灯是前面4个按钮的总和):

■01			■02			■03			
■10			■11	■ □ □ ○	■12	□ ■ □ ○	
■11	■ □ □ ○	■12	□ ■ □ ○	■13	□ □ ■ ○	
■12	□ ■ □ ○	■13	□ □ ■ ○	■14			
●11	■ ■ □ ○	●12	■ ■ ■ ○	●13	□ ■ ■ ○

然后是第二行按钮(和灯的状态相反)

■21	■ ■ □ ●	■22	■ ■ ■ ●	■23	□ ■ ■ ●

然后是第二行灯:

■11	■ □ □ ○	■12	□ ■ □ ○	■13	□ □ ■ ○	
■20			■21	■ ■ □ ●	■22	■ ■ ■ ●	
■21	■ ■ □ ●	■22	■ ■ ■ ●	■23	□ ■ ■ ●	
■22	■ ■ ■ ●	■23	□ ■ ■ ●	■24			
●21	■ □ ■ ○	●22	□ □ □ ●	●23	■ □ ■ ○

以及第三行按钮:

■31	■ □ ■ ●	■32	□ □ □ ○	■33	■ □ ■ ●

最后是第三行灯:

■21	■ ■ □ ●	■22	■ ■ ■ ●	■23	□ ■ ■ ●	
■30			■31	■ □ ■ ●	■32	□ □ □ ○	
■31	■ □ ■ ●	■32	□ □ □ ○	■33	■ □ ■ ●	
■32	□ □ □ ○	■33	■ □ ■ ●	■34			
●31	□ ■ ■ ○	●32	■ ■ ■ ●	●33	■ ■ □ ○

于是我们等到矩阵:

□ ■ ■	■
■ ■ ■	□
■ ■ □	■

解得结果:

■ □ □	■
□ ■ □	□
□ □ ■	■

因此,第一行按钮就是:

■ □ ■

推算出全部按钮为:

■ □ ■
□ ■ □
■ □ ■

下面对 5\times 5 的房间进行矩阵演示:

第一行按钮:

■11	■ □ □ □ □ ○	■12	□ ■ □ □ □ ○	■13	□ □ ■ □ □ ○	■14	□ □ □ ■ □ ○	■15	□ □ □ □ ■ ○

第一行灯:

■00				■01				■02				■03				■04				
■10				■11	■ □ □ □ □ ○	■12	□ ■ □ □ □ ○	■13	□ □ ■ □ □ ○	■14	□ □ □ ■ □ ○	
■11	■ □ □ □ □ ○	■12	□ ■ □ □ □ ○	■13	□ □ ■ □ □ ○	■14	□ □ □ ■ □ ○	■15	□ □ □ □ ■ ○	
■12	□ ■ □ □ □ ○	■13	□ □ ■ □ □ ○	■14	□ □ □ ■ □ ○	■15	□ □ □ □ ■ ○	■16				
○11	■ ■ □ □ □ ○	○12	■ ■ ■ □ □ ○	○13	□ ■ ■ ■ □ ○	○14	□ □ ■ ■ ■ ○	○15	□ □ □ ■ ■ ○

第二行按钮:

■21	■ ■ □ □ □ ●	■22	■ ■ ■ □ □ ●	■23	□ ■ ■ ■ □ ●	■24	□ □ ■ ■ ■ ●	■25	□ □ □ ■ ■ ●

第二行灯:

■11	■ □ □ □ □ ○	■12	□ ■ □ □ □ ○	■13	□ □ ■ □ □ ○	■14	□ □ □ ■ □ ○	■15	□ □ □ □ ■ ○	
■20				■21	■ ■ □ □ □ ●	■22	■ ■ ■ □ □ ●	■23	□ ■ ■ ■ □ ●	■24	□ □ ■ ■ ■ ●	
■21	■ ■ □ □ □ ●	■22	■ ■ ■ □ □ ●	■23	□ ■ ■ ■ □ ●	■24	□ □ ■ ■ ■ ●	■25	□ □ □ ■ ■ ●	
■22	■ ■ ■ □ □ ●	■23	□ ■ ■ ■ □ ●	■24	□ □ ■ ■ ■ ●	■25	□ □ □ ■ ■ ●	■26				
○21	■ □ ■ □ □ ○	○22	□ □ □ ■ □ ●	○23	■ □ □ □ ■ ●	○24	□ ■ □ □ □ ●	○25	□ □ ■ □ ■ ○

第三行按钮:

■31	■ □ ■ □ □ ●	■32	□ □ □ ■ □ ○	■33	■ □ □ □ ■ ○	■34	□ ■ □ □ □ ○	■35	□ □ ■ □ ■ ●	

第三行灯:

■21	■ ■ □ □ □ ●	■22	■ ■ ■ □ □ ●	■23	□ ■ ■ ■ □ ●	■24	□ □ ■ ■ ■ ●	■25	□ □ □ ■ ■ ●	
■30				■31	■ □ ■ □ □ ●	■32	□ □ □ ■ □ ○	■33	■ □ □ □ ■ ○	■34	□ ■ □ □ □ ○	
■31	■ □ ■ □ □ ●	■32	□ □ □ ■ □ ○	■33	■ □ □ □ ■ ○	■34	□ ■ □ □ □ ○	■35	□ □ ■ □ ■ ●	
■32	□ □ □ ■ □ ○	■33	■ □ □ □ ■ ○	■34	□ ■ □ □ □ ○	■35	□ □ ■ □ ■ ●	■36				
○31	□ ■ ■ ■ □ ○	○32	■ ■ □ ■ ■ ○	○33	■ □ ■ □ ■ ●	○34	■ ■ □ ■ ■ ○	○35	□ ■ ■ ■ □ ○

第四行按钮:

■41	□ ■ ■ ■ □ ●	■42	■ ■ □ ■ ■ ●	■43	■ □ ■ □ ■ ○	■44	■ ■ □ ■ ■ ●	■45	□ ■ ■ ■ □ ●

第四行灯:

■31	■ □ ■ □ □ ●	■32	□ □ □ ■ □ ○	■33	■ □ □ □ ■ ○	■34	□ ■ □ □ □ ○	■35	□ □ ■ □ ■ ●	
■40				■41	□ ■ ■ ■ □ ●	■42	■ ■ □ ■ ■ ●	■43	■ □ ■ □ ■ ○	■44	■ ■ □ ■ ■ ●	
■41	□ ■ ■ ■ □ ●	■42	■ ■ □ ■ ■ ●	■43	■ □ ■ □ ■ ○	■44	■ ■ □ ■ ■ ●	■45	□ ■ ■ ■ □ ●	
■42	■ ■ □ ■ ■ ●	■43	■ □ ■ □ ■ ○	■44	■ ■ □ ■ ■ ●	■45	□ ■ ■ ■ □ ●	■46				
○41	□ □ □ □ ■ ●	○42	□ □ □ ■ □ ○	○43	□ □ ■ □ □ ○	○44	□ ■ □ □ □ ○	○45	■ □ □ □ □ ●

第五行按钮:

■51	□ □ □ □ ■ ○	■52	□ □ □ ■ □ ●	■53	□ □ ■ □ □ ●	■54	□ ■ □ □ □ ●	■55	■ □ □ □ □ ○

第五行灯:

■41	□ ■ ■ ■ □ ●	■42	■ ■ □ ■ ■ ●	■43	■ □ ■ □ ■ ○	■44	■ ■ □ ■ ■ ●	■45	□ ■ ■ ■ □ ●	
■50				■51	□ □ □ □ ■ ○	■52	□ □ □ ■ □ ●	■53	□ □ ■ □ □ ●	■54	□ ■ □ □ □ ●	
■51	□ □ □ □ ■ ○	■52	□ □ □ ■ □ ●	■53	□ □ ■ □ □ ●	■54	□ ■ □ □ □ ●	■55	■ □ □ □ □ ○	
■52	□ □ □ ■ □ ●	■53	□ □ ■ □ □ ●	■54	□ ■ □ □ □ ●	■55	■ □ □ □ □ ○	■56				
○51	□ ■ ■ □ ■ ○	○52	■ ■ ■ □ □ ●	○53	■ ■ □ ■ ■ ●	○54	□ □ ■ ■ ■ ●	○55	■ □ ■ ■ □ ○

得到矩阵:

□ ■ ■ □ ■	■	
■ ■ ■ □ □	□	
■ ■ □ ■ ■	□	
□ □ ■ ■ ■	□	
■ □ ■ ■ □	■

解得:

■ □ □ □ ■	■	
□ ■ □ ■ □	■	
□ □ ■ ■ ■	□	
□ □ □ □ □	□	
□ □ □ □ □	□

可以看到,这个矩阵也不是满秩的(最后两行为0),这与之前 25\times 25 的矩阵的情况是一样的。这说明解不是唯一的。当然我们也可以用矩阵求逆的方法计算出所有可能的解,这里就不再赘述了。因此,第一行按钮的状态可以是:

■ ■ □ □ □

最后结果是:

■ ■ □ □ □
■ ■ □ ■ ■
□ □ ■ ■ ■
□ ■ ■ ■ □
□ ■ ■ □ ■

由第一行按钮状态推算出最后的矩阵方程需要遍历所有的按钮和灯一次一行内所有在按钮,时间复杂度为 O(n^3)

计算这个 n\times n 的矩阵的解需要高斯消元,时间复杂度为 O(n^3)

由第一行按钮状态推算出全部的按钮状态需要也遍历所有的按钮和灯一次,时间复杂度也为 O(n^2)

综上所述,该算法总体的时间复杂度为 O(n^3)

关于灯非全暗的初始状态

1. 完全穷举法, O(2^{n^2})

初始状态已经亮的灯不需要转变状态,就相当于如果这个灯暗则继续保持暗的状态,因此这个时候只需要修改一下判定最终灯状态的条件就行。

2. 首行穷举法, O(2^n)

因为有些灯已经亮着了,计算按按钮后灯的状态时只需要把初始的状态也考虑进去就行。

3. 完全方程法, O(n^6)

如果用的是高斯消元法,我们只需要修改一下方程右边灯的增广矩阵就行。如果直接求矩阵的逆,那么我们只需要把那些需要转变状态的灯的行(而不是所有行)相加即可。

4. 首行方程法, O(n^3)

同算法2,在计算灯的最终状态的时候,我们只需要把灯的初始状态也考虑进去就行。

关于其它规则

只要满足算法3中的对称性,我们依然可以使用算法1-4的套路进行计算。

对于 n\times m 的房间,我们只需少许修改算法,依然可以进行计算。

更多结论

关于点灯游戏的变体以及更多有趣的数学结论可以参考这篇文章:

Lights Out Mathematics​www.jaapsch.net
编辑于 2018-12-31

文章被以下专栏收录