小游戏 Lights Out (关灯) 的求解 —— 异或方程组

Author : Evensgn 
Blog Link : http://www.cnblogs.com/JoeFan/
Article Link : http://www.cnblogs.com/JoeFan/p/4338003.html
 

游戏介绍

Lights Out (关灯)是一款据说在20世纪90年代就已经被设计出的小游戏,游戏的玩法十分简单。

首先,给定一个 n 行 m 列的矩形方格阵,每个格子上都有一盏灯。

初始时,有些灯是开着的,有些灯是关着的。

玩家每次进行一次操作,选中一盏灯,点击一下它,就会将它和与它相邻的灯的状态改变,即开着的灯变为关闭,关着的灯变为开启。

最后的目的是关闭所有的灯。

我没有找到原版 Lights Out 的网页版演示,但是我找到了博客园一位博友DIY的一个类似的游戏,蓝色拼图

这个版本的蓝色拼图与 Lights Out 实质上相同,只是初始状态固定了是所有的灯都是开启的。

Lights Out 的游戏规则就是这样简单,然而到了后面的几关,方格增多,情况复杂,人工找出解法对于我来说是十分困难的。

因此,我们考虑用程序求解这个游戏。

求解方法

首先我们要将这个游戏的过程转化为数学模型。

显然地,对于一个方格,会影响到它的方格只有它本身和与它相邻的 4 个方格(对于边界的方格来说,相邻的方格不足 4 个)。

并且很容易发现,每一个方格我们要么不点击,要么点击 1 次,因为点击一个方格两次及以上是没有任何意义的。每点击两次就相当于没有点击。

对于方格 i ,我们用 0 表示不点击它,用 1 表示点击它,记作 Si 。

每盏灯的状态只有开或者关,我们用 0 和 1 表示方格 i 状态,方格 i 的初始状态记为 Mi 。

可以看出,每盏灯 i 的最终状态只与 Mi + Si + Sk1 + Sk2 + .... + Skp  (k1 ... kp 是枚举与 i 相邻的所有方格)的奇偶性有关。

既然只与奇偶性有关,我们就可以用异或运算来表示它。

也就是说,对于每盏灯 i ,我们都可以得到一个方程       Mi xor Si xor Sk1 xor Sk2 xor ... xor Skp = 0 。

等式右边的 0 表示最后每盏灯的状态都是关闭的。

这个方程其实也就等价于        Si xor Sk1 xor Sk2 xor ... xor Skp = Mi 。

我们得到了 Tot 个这样的方程(Tot 是灯的数量,即 Tot = n * m),共有 Tot 个未知数(即 Tot 个 Si),就是一个异或方程组。

由于游戏给定的初始状态一定有解,所以我们是一定可以求出这个异或方程组的一组解的。

那么下面的问题就是:怎样求解异或方程组?

显然,我们要使用高斯消元来求异或方程组的解。

这个过程与使用高斯消元求解普通的线性方程组相似(如果不了解高斯消元可以看一下Wikipedia-高斯消去法),只是每次在一个方程中消去一个未知数的时候,不是将这个方程乘上一个系数后与另一个方程相减,而是将这个方程的系数与另一个方程的系数进行异或运算,两个方程右边的数也要一起进行异或。

这样就可以求出 Lights Out 的解了。

代码如下,因为代码非常简单所以没有添加注释:

#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>

using namespace std;

const int MaxL = 10 + 5, MaxN = 100 + 5;
const int Dx[5] = {0, 0, 1, -1}, Dy[5] = {1, -1, 0, 0};

int n, m, Tot;
int A[MaxN][MaxN], Map[MaxL][MaxL], Ans[MaxL][MaxL];

inline bool Inside(int x, int y)
{
	if (x < 0 || x >= n) return false;
	if (y < 0 || y >= m) return false;
	return true;
}

inline int Get_Index(int x, int y)
{
	return x * m + y + 1;
}

struct Pos
{
	int x, y;
};

inline Pos Get_Pos(int Num)
{
	Pos ret;
	ret.x = (Num - 1) / m;
	ret.y = ((Num % m - 1) + m) % m;
	return ret;
}

void Get_Equation(int x, int y)
{
	int Id, Id2, xx, yy;
	Id = Get_Index(x, y);
	for (int i = 1; i <= Tot; ++i) A[Id][i] = 0;
	A[Id][Tot + 1] = Map[x][y];
	A[Id][Id] = 1;
	for (int k = 0; k < 4; ++k)
	{
		xx = x + Dx[k]; yy = y + Dy[k];
		if (!Inside(xx, yy)) continue;
		Id2 = Get_Index(xx, yy);
		A[Id][Id2] = 1;
	}
}

inline void Swap(int p, int q)
{
	int Temp;
	for (int i = 1; i <= Tot + 1; ++i)
	{
		Temp = A[p][i];
		A[p][i] = A[q][i];
		A[q][i] = Temp;
	}
}

void Gauss()
{
	int Tj;
	for (int i = 1; i <= Tot; ++i)
	{
		Tj = i;
		for (int j = i + 1; j <= Tot; ++j)
		{
			if (A[Tj][i] == 0 && A[j][i] == 1)
			{
				Tj = j;
				break;
			}
		}
		if (A[Tj][i] == 0) continue;
		if (Tj != i) Swap(Tj, i);
		for (int j = i + 1; j <= Tot; ++j)
		{
			if (A[j][i] == 0) continue;
			for (int k = i; k <= Tot + 1; ++k)
				A[j][k] ^= A[i][k];
		}
	} 

	Pos Pi;
	for (int i = Tot; i >= 1; --i)
	{
		Pi = Get_Pos(i);
		Ans[Pi.x][Pi.y] = A[i][Tot + 1];
		for (int j = i - 1; j >= 1; --j)
			if (A[j][i]) A[j][Tot + 1] ^= A[i][Tot + 1];
	}
}

int main()
{
	scanf("%d%d", &n, &m);
	Tot = n * m;
	for (int i = 0; i < n; ++i)
		for (int j = 0; j < m; ++j)
			scanf("%1d", &Map[i][j]);
	for (int i = 0; i < n; ++i)
		for (int j = 0; j < m; ++j)
			Get_Equation(i, j);
	Gauss();

	printf("Solution:\n");
	for (int i = 0; i < n; ++i)
	{
		for (int j = 0; j < m; ++j)
			printf("%d", Ans[i][j]);
		printf("\n");
	}
	return 0;
}

  

时间: 2024-10-24 02:57:54

小游戏 Lights Out (关灯) 的求解 —— 异或方程组的相关文章

高斯消元法求解异或方程组: cojs.tk 539.//BZOJ 1770 牛棚的灯

高斯消元求解异或方程组: 比较不错的一篇文章:http://blog.sina.com.cn/s/blog_51cea4040100g7hl.html cojs.tk  539. 牛棚的灯 ★★☆   输入文件:lights.in   输出文件:lights.out   简单对比时间限制:1 s   内存限制:128 MB [问题描述] 贝希和她的闺密们在她们的牛棚中玩游戏.但是天不从人愿,突然,牛棚的电源跳闸了,所有的灯都被关闭了.贝希是一个很胆小的女生,在伸手不见拇指的无尽的黑暗中,她感到惊

【poj1830-开关问题】高斯消元求解异或方程组

第一道高斯消元题目~ 题目:有N个相同的开关,每个开关都与某些开关有着联系,每当你打开或者关闭某个开关的时候,其他的与此开关相关联的开关也会相应地发生变化,即这些相联系的开关的状态如果原来为开就变为关,如果为关就变为开.你的目标是经过若干次开关操作后使得最后N个开关达到一个特定的状态.对于任意一个开关,最多只能进行一次开关操作.你的任务是,计算有多少种可以达到指定状态的方法.(不计开关操作的顺序)0<=N<=29 我们用样例来模拟一下: 我的高斯消元求解异或方程组模版: 1 int gauss

高斯消元求解异或方程组

POJ1830 开关问题 对于解异或方程组,系数可以采用二进制压缩,如果系数太多可以使用bitset,但是如果少一点就可以使用下述的写法,更加简单快速 使用bitset的写法更正常的没什么区别,只是对应的消除变为异或操作,另外行变换也会更加简单 #include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; int a[100

POJ 1222-EXTENDED LIGHTS OUT(高斯消元求解异或方程组)

题目地址:POJ 1222 题意:有一个5*6的矩阵,每个位置都表示按钮和灯,1表示亮,0表示灭.每当按下一个位置的按钮,它和它周围灯的状态全部翻转(题目中给出如何影响),问在这样的一个方阵中按下哪些按钮可以把整个方阵都变成灭的,这时1表示按了,0表示没按. #include <stdio.h> #include <math.h> #include <string.h> #include <stdlib.h> #include <iostream>

BZOJ.1923.[SDOI2010]外星千足虫(高斯消元 异或方程组 bitset)

题目链接 m个方程,n个未知量,求解异或方程组. 复杂度比较高,需要借助bitset压位. 感觉自己以前写的(异或)高斯消元是假的..而且黄学长的写法都不需要回代. //1100kb 324ms #include <cstdio> #include <cctype> #include <bitset> #include <algorithm> const int N=1004,M=2004; int n,m; char s[N]; std::bitset&l

自制Unity小游戏TankHero-2D(4)关卡+小地图图标+碰撞条件分析

自制Unity小游戏TankHero-2D(4)关卡+小地图图标+碰撞条件分析 我在做这样一个坦克游戏,是仿照(http://game.kid.qq.com/a/20140221/028931.htm)这个游戏制作的.仅为学习Unity之用.图片大部分是自己画的,少数是从网上搜来的.您可以到我的github页面(https://github.com/bitzhuwei/TankHero-2D)上得到工程源码. 本篇主要记录关卡解析器.小地图图标和对碰撞的原理的探索,需要耐心分析. 关卡解析器 在

js选择颜色小游戏(随机生成不含重复数字的数组,通过数组中的数控制定义好的数组)

<!DOCTYPE html><html> <head> <meta charset="utf-8"> <title>js网页版小游戏</title> <style media="screen"> .wrap { width: 577px; outline: 1px solid hotpink; margin: 100px auto; box-shadow: 0 0 5px; } .

使用AxureRP7.0制作经典小游戏"大家来找茬"

本案例是<网站蓝图AxureRP7.0从入门到精通视频教程>中的最后一节,适用于对Axure基础知识掌握比较熟练的同学:教程由axure原型库网站录制,转载请注明出处!相信很多刚接触Axure或者学习了一段时间但并没有深入的同学们,看到这个案例一定很惊讶,使用Axure竟然能做出如此逼真的交互效果!通过此案例也可以激发广大同学们学习Axure的热情和信心!试想一下,如果你有情侣的话,把你们珍藏的合影拿出来处理几张,做成大家来找茬的小游戏,不但锻炼了自己的技能,还能给对方一个惊喜,岂不是一举两得

使用AxureRP7.0制作经典数独小游戏原型,axure游戏原型下载

之前,有同学在Q群中提问,如何使用axure制作经典数独解谜小游戏,当时由于时间关系没有来得及亲手制作,而是给同学们提供了Axure6.5版本的一个数独解谜游戏的原型,本教程由axure原型库网站录制,转载请注明出处!但是那个原型做的太过繁杂,所以仅供大家参考交流:在此,金乌老师特地抽时间给同学们使用AxureRP7.0制作了一下,感觉对实战逻辑分析和axure变量的掌握比较有考验,所以就放出来供大家学习交流使用. 在学习的过程中,如果你仅凭自己现有的对axure的掌握,无法准确分析并组织出原型