浅谈点灯游戏O(n^3)算法
点灯游戏(POJ1681/NKOJ P1987)
很显然的高斯消元,将\(n \times n\)的矩阵弄成一行,对其相邻格子的系数为1即可(\(a[i][j]\)表示点\(i\)号格子,\(j\)号格子是否受到影响),最后解方程即可。
NKOJ P8530 手机游戏 超级版
题意同NKOJ P1987,只不过数据范围从\((1 \leq n \leq 40)\)到了\((1 \leq n \leq 1000)\)
一眼不可做,直接用P1987的做法时间复杂度为\(O(n^6)\),不可行
考虑到只有\(0/1\)取值,用\(bitset\)优化常数,依然不可行
继续考虑暴力做法,假设我们已经确定了第一行点了哪些格子,我们可以推出\(2-n\)行每行的状态(点或不点)
即对于第\(i\)行的'#',通过\(i+1\)的点击使其变成'.',这时候对于\(i+1\)行无法再继续点击(不然维护的\(i\)行不再合法),那么对于\(i+1\)行的'#',只能通过点击\(i+2\)行消掉,以此类推,直到得到最后一行无法被消除的状态。
说的简单一点就是对于第\(i\)行,通过只点第\(i\)行的方式让\(i-1\)行全部为'.',当讨论完后,\(1\) 到 \(i-1\) 全都是'.'
举个例子:
对于以下这个矩阵
.##
.#.
...
讨论点击第2行,修改第1行
点击(2,2)
..#
#.#
.#.
点击(2,3)
...
##.
.##
此时第1行已全被消除,考虑第2行
点击(3,1)
...
.#.
#.#
点击(3,2)
...
...
.#.
第2行也被消完,此时第3行还剩下一个无法被消除
显然,做完上述操作后,整个矩阵除了最后一行,其他一定是'.'
回到原题,题目是要求出给定矩阵使其全部熄灭的具体点击方案
我们发现在确定第一行状态后,我们可以推出其余行的状态(保证已经讨论过的行为熄灭)
那么从一个全熄灭状态转移到一个矩阵状态,等效于将这个矩阵全部熄灭,所以我们对于题目给出的矩阵跑一遍上述的过程,得到最后一行状态。
接着我们思考,这个东西有什么用?发现,如果一空矩阵点击了一些点后,最后一行状态(其余行全部为熄灭)等于了题目给定矩阵的最后一行状态,则这些点击的点被点后,可以将目标矩阵消完(也可以理解成
将空矩阵点成目标矩阵)
为了接下来的表述,我们把目标矩阵求的的最后一行设为\(A[...]\)
发现了以上特征后,我们开始考虑在全灭矩阵上如何使得点一些点让最后一行状态等于\(A[]\)
因为已经发现第一行的状态就可以决定最后一行,那么我们考虑枚举第一行状态,\(O(2^n)\),一样炸裂(好像炸的跟裂了)
于是怎么办呢?(我白银你问我?GG)
其实很简单 (毒瘤),我们慢慢来,先讨论只点1个的情况,很简单对吧,直接点,然后往下讨论就行了,那么如果点2个呢,也很简单,可以看成先点1个,再点1个,对于重复了的部分就相当于没点,这个操作是不是有点像异或?那么对于点\(k\)个点,最后一行的状态就是把这\(k\)个点单独的状态异或起来,就完了,是不是很简单。
于是,我们可以\(O(n^3)\)预处理出每个位置点击后最后一行的\(B[]\)状态,发现时间复杂度有点过分,但只有\(0/1\)取值,bitset优化即可
接着就是如何找出选出第1排的那些点,可以使得\(B[]\)异或之和等于\(A[]\)
发现单独列之间的影响是独立的,即\(A[j]\)只会受到\(B[][j]\)的影响,与其他列无关,可以看成方程组。
考虑高斯消元
把状态数组竖着摆放成为方程矩阵,\(x_i\)的值表示第1行\(i\)号点是否需要点击,矩阵的值即为系数
接着解一个异或方程组就完事了
时间复杂度\(O(n^3/64)\),完全跑得过