必威体育Betway必威体育官网
当前位置:首页 > IT技术

关灯游戏 Lights out (三)(线性代数+高斯消元,搜索全部解)

时间:2019-07-17 09:42:18来源:IT技术作者:seo实验室小编阅读:87次「手机版」
 

关灯游戏

关灯游戏和线性代数联系紧密,对于一个 的灯阵,用线性方程组+高斯消元法求解,时间复杂度为O(m×n)^3。相对于首行枚举算法复杂度O(2^n) ,线代算法的时间复杂度低很多。用线性代数求解关灯游戏是个很不错的选择。

本文用最通俗的语言介绍线代求解关灯游戏原理。由于解释通俗化,细节之处难以严谨表述,说得不好的地方,请大神轻拍。

线性方程组求解关灯游戏的原理

问题:

一个2×2灯阵,初始局面右下方3盏灯亮,左上角一盏灯不亮,如下图所示。请用线性代数求解,找到可以使得所有灯都熄灭的开关操作。

解:

我们对四盏灯的开/关操作分别记为 x1、x2 、x3 、x4 ,如下图所示:

我们用1代表操作,用0代表未操作,即 的取值为0或1。对四盏灯的亮/灭状态分别记为 L1、L2 、L3 、L4 ,如下图所示:

我们用1代表亮着的灯,用0代表熄灭的灯,根据局面的初始状态,很显然有

接下来观察局面,

能影响L1灯的亮/灭状态的操作只有 x1、x2 、x3 ;

能影响L2灯的亮/灭状态的操作只有 x1、x2 、x4 ;

能影响L3灯的亮/灭状态的操作只有 x1、x3 、x4 ;

能影响L4灯的亮/灭状态的操作只有 x2、x3 、x4 ;

定理1:对同一盏灯开/关操作偶数次,等于操作0次;对同一盏灯操作奇数次,等于操作1次。

所以我们将 x1、x2、x3、x4 及其系取值限定在0或者1两个数值,以后不再说明(不怕蛋疼的话,可以对操作次数不限定,这样一来将有无穷多个解,这些解都是在基础解上对每盏灯再重复操作偶数次,本质上相同)。

假如一开始所有灯都熄灭,并且假设将 x1、x2、x3、x4 全体操作一遍后,灯阵的亮/灭状态为当前状态。根据定理1,那么将 x1、x2、x3、x4 全体操作一遍,灯阵就又回到全熄灭状态。x1、x2、x3、x4 就是我们想要的答案,于是列方程组:

游戏问题转换成了求解方程组问题。上面方程组不难求解,用初中的知识足够了。有一点要注意,方程组是在二元域{0,1}内求解,也就是运算过程中,所有数值的取值范围都只能是0或者1。二元域运算规则和常规运算规则有所不同,具体如下(不要问我二元域运算规则什么是这样的,要问就问什么是灯?或者灯为什么会亮?之类的问题):

加法:0+0=0,0+1=1,1+0=1,1+1=0

减法:0-0=0,0-1=1,1-0=1,1-1=0

乘法:0×0=0,0×1=0,1×0=0,1×1=1

除法:0/0=无意义,0/1=0,1/0=无意义,1/1=1

注:有限域上的运算符号并不是上面那个样子,而是一些圈、叉之类的奇怪符号。这里一方面不方便打出这些符号,另一方面也为了让读者便于理解,所以仍旧采用“+、-、×、/”形式。我们看到,加减法都等价于二进制位运算中的“亦或”运算;乘法等价于二进制位运算中的“与”运算;除法在这里用不到,直接无视。

方程组求解结果为:

这是方程组唯一的解,所以这个游戏局面解法是唯一的,标记在游戏局面见下图:

到这里,线代原理就介绍完毕了。怎么样,线代解法原理很简单吧,无非就是求解线性方程组问题。而计算机求解线性方程组,我们在增广矩阵中用高斯消元法,时间复杂度为O(m×n)^3,判断游戏有没有解,可以用矩阵的秩来判断方程组有没有解,有多少个解。

用线性代数搜索所有解法的C/C++代码如下:

#include<iOStream>
using namespace std;
int Row,Column,MAXN;

void init(int **matrix)                                            //初始化系数矩阵
{
	int i,j,k;
	for(i=0; i<Row; i++)
		for(j=0; j<Column; j++)
		{
			k=i*Column+j;
			matrix[k][k]=1;
			if(i>0)matrix[(i-1)*Column+j][k]=1;
			if(i<Row-1)matrix[(i+1)*Column+j][k]=1;
			if(j>0)matrix[i*Column+j-1][k]=1;
			if(j<Column-1)matrix[i*Column+j+1][k]=1;
		}
}

void  Gauss(int **matrix,int *solution)                            //高斯消元
{
	int i,j,k,x,y=0,rand_coefficient,rand_matrix,rank1,rank2,inc=0;
	for(k=0; k<MAXN&&y<MAXN; k++,y++)                          //增广矩阵转换为阶梯矩阵
	{
		x=k;
		for(i=k+1; i<MAXN; i++)
			if(matrix[i][y]>matrix[x][y])x=i;
		if(x-k)                                            //交换矩阵行数据
			for(j=y; j<MAXN+1; j++)
			{
				matrix[k][j]=matrix[k][j]^matrix[x][j];
				matrix[x][j]=matrix[k][j]^matrix[x][j];
				matrix[k][j]=matrix[k][j]^matrix[x][j];
			}
		if(matrix[k][y]==0)
		{
			k--;
			continue;
		}
		for(i=k+1; i<MAXN; i++)                             //消元
			if(matrix[i][y])
				for(j=y; j<MAXN+1; j++)matrix[i][j]^=matrix[k][j];
	}
	for(rand_coefficient=rand_matrix=i=0; i<MAXN; i++)          //计算系数矩阵的秩和增广矩阵的秩
	{
		for(rank1=rank2=j=0; j<=MAXN; j++)
		{
			if(j<MAXN)rank1|=matrix[i][j];
			rank2|=matrix[i][j];
		}
		rand_coefficient+=rank1;
		rand_matrix+=rank2;
	}
	if(rand_coefficient<rand_matrix)                            //系数矩阵秩小于增广矩阵秩,局面无解
		cout<<"此局面无解!\n\n";
	else                                                        //枚举并回代求解
	{
		for(k=1<<(MAXN-rand_coefficient); k>0; k--)
		{
			for(i=MAXN-1; i>rand_coefficient; i--)
			{
				matrix[i-1][MAXN]+=matrix[i][MAXN]>>1;
				matrix[i][MAXN]&=1;
			}
			for(i=0; i<MAXN; i++)solution[i]=matrix[i][MAXN];
			for(i=MAXN-1; i>=0; i--)                    //回代
				for(j=i-1; j>=0; j--)
					if(matrix[j][i])solution[j]^=solution[i];
			for(i=0; i<MAXN; i++)                       //输出答案
				(i%Column)?cout<<solution[i]<<" ":cout<<endl<<solution[i]<<" ";
			inc++;                                      //解数量计数器
			printf("\n");
			matrix[MAXN-1][MAXN]++;
		}
		cout<<"\n共计"<<inc<<"个解\n";
	}
}

int main(void)
{
	int i,j;
	char C;
	cout<<"\n                    关灯游戏(Lights out)解题程序(线代解法)\n";
	cout<<"请输入行数:";
	cin>>Row;
	cout<<"请输入列数:";
	cin>>Column;
	MAXN=Column*Row;
	int **matrix=new int*[MAXN+4];                            //增广矩阵,二维数组
	for(i=0; i<MAXN+4; i++)matrix[i]=new int [MAXN+4];
	int *solution=new int[MAXN+2];                            //解数组
	for(i=0; i<MAXN+4; i++)
		for(j=0; j<MAXN+4; j++)matrix[i][j]=0;
	init(matrix);                                             //初始化系数矩阵
	cout<<"请选择,是否输入指定初始局面?(Y/N):",cin>>C;
	if((C=='Y')||(C=='y'))
	{
		cout<<"请输入初始局面(1代表亮灯,0代表灭灯,灯与灯之间用空格隔开,换行用回车):\n";
		for(i=0; i<MAXN; i++)cin>>matrix[i][MAXN];
	}
	else for(i=0; i<MAXN; i++)matrix[i][MAXN]=1;              //默认初始化局面为灯全亮  
	Gauss(matrix,solution);
	for(int i=0; i<MAXN+4; i++)delete []matrix[i];
	delete []matrix;
	delete []solution;
	cout<<"\n\n求解完毕,请按任意键退出程序。";
	cin.ignore(),cin.ignore();                                //暂停显示
	return 0;
}
    上面代码可以搜索任意局面的全部解,允许用户输入任意初始局面,已经非常方便使用了。如果还觉得使用上面代码太麻烦,笔者将其制作成软件,截图如下:

软件在这里下载:关灯游戏解题程序1.0版 https://pan.baidu.com/s/1geNMxcZ

相关阅读

要优雅 不要污:苍老师们如何在搜索引擎中帮自己洗白?

萨莎 &middot; 格雷(Sasha Grey)是本世纪最为引人注目的色情明星之一,在她的色情电影职业生涯中,其经典的印度少女扮相以及曾经拍过的

产品调研报告:TapTap,走近玩家发现好游戏

本篇文章为大家分析了一款第三方游戏下载平台——TapTap ,TapTap 致力于向玩家推荐真正好玩的正版游戏,以及向开发者提供测试、反馈

Tracert(traceroute)&Ping 工作原理分析

一、tracert工作过程分析 Tracert 命令用 IP 生存时间 (TTL) 字段和 ICMP 错误消息来确定从一个主机到网络上其他主机的路由。首

原生js做打地鼠游戏

学原生js的语法其实是容易的,最主要的练习逻辑思维。对于刚刚学完js的语法的我来说,写这个打地鼠游戏真的花费了我一整天的时间。整

在淘宝游戏交易平台如何购买?

在淘宝游戏交易平台如何购买? 第一步:找到宝贝后,点击立即购买。 第二步:在接下来的页面中填写收货信息,请务必认真填写,否则将无法收

分享到:

栏目导航

推荐阅读

热门阅读