puzzle(1411)《消除》2048最大分数

前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家:点击跳转

目录

〇,问题

一,在线2048(无悔版)

二,2048悔一步版

三,2048简化版

四,2048能玩到多大的数字

五,2048能玩到多少分

1,分析

2,构造数据

3,验证数据正确性

4,计算得分

六,2048格子位置判定


〇,问题

2048大家都玩过,也有很多人考虑过,最高到底能玩到多大的数字。

也有的人会问,最高能玩到多大的分数,但是这个明显要复杂很多。

一,在线2048(无悔版)

有个在线的2048做的还不错,手机电脑都可以玩,

而且只要打开,后面就再也不需要网络了,火车上面玩很方便

链接:2048 Game - Play 2048 Game Online

我的成绩:

这个游戏我还录了一个视频,8分钟完成2048,资源分享汇总

二,2048悔一步版

我的手机里面还安装了一个可以悔一步的版本的2048

可以悔一步的版本,比不能悔的版本要简单很多。

比如说,当只有一个空格的时候,很多时候,出现2就会game over,出现4就可以继续玩。

这样,利用悔一步的功能,可以不断尝试,直到出现4再继续玩。

当然,有的版本是game over之后就不能悔一步了

那就比这个版本稍微难一些,但是还是比不能悔的版本要简单一些。

我的成绩:

三,2048简化版

简化一:假设系统只会给出2,不会给出4

简化二:假设16个格子没有位置的限制,任何2个格子只要数相等就可以合并。

重定义操作:每次操作,可以同时合并若干对数,比如将2,2,4,4,8合并成4,8,8,

如果没有可以合并的数,就不合并,算一次不做任何改变的操作。

每次操作都会产生1个新的2

问题:这样的游戏最多可以玩出多大的数字?

首先列出2的幂,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768,65536,131072

131072=2^17

下面我将证明,永远不可能玩到131072

定义一个全局状态:所有格子的数的和S

很明显,刚开始的时候S是4,每合并1次,S的值就会加2

假设某个时候出现了131072,那么S至少为131072

那么一定有某个时候,S=131070,表示成二进制是1111 1111 1111 1111 0

这个数,要想表示成2的幂的和,至少需要16个数即从2到65536这16个数。

而这个时候,没有多余的空格了,所以说,不可能玩到131072。

而且,65536是可以达到的。

四,2048能玩到多大的数字

按照上面的论述可以推出,如果2048永远只出2的话,是不可能玩到131072的。

但是真正的2048,是有可能出现4的,所以说,2048永远不可能玩到262144,但是有没有可能玩到131072很难用理论说明。

为了研究131072到底能不能达到,我意识到只能悔一步的版本无法满足我的要求,于是我开始玩第三种版本——无限悔棋版(下面是我的成绩)

APK下载 资源分享汇总

其实,可以悔一步的版本和可以悔任意多步的版本,在理论上是效果一样的。

当我们考虑2048最多可以玩到多大的数字的时候,是这么想的:

每次出现的是2还是4是随机的,出现的位置也是随机的。

我们只需要考虑在所有情况中,对我们最有利的那一种即可。

也就是说,可以这样考虑,每次出现的是2还是4是由我们来定,每次出现的位置也由我们来定,这样的情况下最多可以玩到多大的数字。

不难发现,无论是可以悔一步的版本的2048,还是可以悔任意多步的,都和这个模型的等价的。

这个图说明131072是可以玩到的,也让人很容易相信,131072便是能玩出的最大数字了,要证明也简单,就像我上面证明的那样。

五,2048能玩到多少分

1,分析

虽然我已经玩到了终点,已经无法再玩了,但是分数却不是最高的。

2048的计分规则是,玩家通过合并得到1个数N,这个数就计分N

比如说,2个2合并得到4,就加4分,2个1024合并得到2048,就加2048分

所以要计算最高分需要注意一个问题,同样是4,如果是玩家合并2个2得到的,那就是4分,但是如果是自动给出的4,那是没有分的,也就是说,单看这16个格子里面有哪些数是无法判断准确的得分的。

不过,最高分对应的局面只有2种情况,最小的格子可能是2也可能是4,另外15个格子就一定是8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768,65536,131072无疑了。

为了计算最高分,可以再次假设每次给出的数都是2,如果还能这样玩出上述的局面的话,那就一定能得到最高分。

具体计算方法是,2没分,4对应4分,8对应16分,16对应48分,32对应128分,64对应320分,128对应768分,256对应1792分,512对应4096分,1024对应9216分,2048对应20480分,4096对应45056分,8192对应98304分,16384对应212992分,32768对应458752分,65536对应983040分,131072对应2097152分

所以从8到131072这15个格子的总分是3932160分,但是这个分数是不可达到的,因为如果每次都出2的话,是不可能玩出131072的,也就是

在玩出131072之前至少出1个4,

同理在玩出131072之后,在玩出65536之前至少出1个4,

在玩出131072和65536之后,在玩出32768之前至少出一个4

。。。。。。

在玩出131072,65536,32768......128,64,32,16之后,在玩出8之前至少出1个4,

所以2048的理论最高分是3932160-15*4=3932100分

这个应该是可达到的,但是很难在理论上证明。

下面我将编程+构造证明,3932100分是可以达到的,这样便证明了2048的最高分是3932100

我的证明工作分为3步:

(1)构造数据

构造出1个多行数据,每一行对应2048的一个状态,第一行是初始状态(只有2个2),最后一行是最终状态(16个数分别是4,8,16......131072)

(2)验证数据正确性

编写一个程序验证每一行到下一行的转化都是对的,符合2048的规则

(3)计算得分

其实只要验证一共只出现过16个4,那么得分就是最高分3932100

2,构造数据

要想构造数据,首先需要自己编写一个2048的程序,只需要1个控制台的程序即可

(1)需要的主要功能如下:

(1.1)对记事本进行读写,把每一个状态和操作写到记事本的一行,下一次玩的时候读取记事本即可接着玩

(1.2)显示游戏界面,读取键盘的上下左右键,如果操作合法的话,进行相应的操作

(1.3)每次操作之后,要给出1个数,包含3个数:行i,列j,在i行j列出2还是4

(2)规定数据结构

每一行放20个数,对应int s[21];的后面20个数,第0个数废弃不用

第1-16分别对应游戏界面中的16个数,0代表空格

例如,当s[1]到s[16]分别为2 0 0 0 2 0 0 0 16 0 0 0 16 2 0 0时,对应的状态就是

s[17]对应玩家操作,也就是上下左右,-4是上,4是下,-1是左,1是右

具体说来,s[17]代表的是玩家从上一行对应的状态变成本行对应的状态所进行的操作

s[18]是行,范围是0-3,s[19]是列,范围是0-3,s[20]是新出的数。

也就是说,自上一行前面16个数对应的状态开始,玩家进行了s[17]对应的操作,然后系统在s[18]、s[19]对应的位置新出了一个数s[20],然后状态就变成了本行前面16个数对应的状态

以我玩的结果举例,前2行是

0 0 0 0 0 0 0 0 2 0 0 0 2 0 0 0 0 0 0 0
2 0 0 0 0 0 0 0 0 0 0 0 4 0 0 0 4 0 0 2 

(第一行的后面4个数没有意义)

也就是说初始状态是

第二行后面的4 0 0 2表示,我进行了4对应的下操作,即往下滑,然后系统自动在第0行第0列新出1个2

那么此时新的状态就是

也就是对应第二行前面的16个数。

(3)判断新出的数的位置和大小

首先大小,是2还是4,其实很容易确定,

整个游戏中出现的最后一个数,无论是2还是4都不重要,都无法得分,在此之前有15次是必须要出4,其他的时候都必须要出2,这样才能达到最高分。

这15个状态和其他所有的状态很容易区分出来,即如果有1个空格和15个不同的数,而且其中没有2,那么此时就必须出4

代码:

int newNum(int *p)
{
	int s = 0, k, num0 = 0;
	for (int i = 1; i <= 16; i++)
	{
		k = *(p + i);
		if ((s^k) < s)return 2;//非0数不重复,否则出2
		if ((s^k) == s)num0++;//统计空格数量
		if (s % 4)return 2;//没有2,否则出2
		s ^= k;
	}
	if (num0 == 1)return 4;
	return 2;
}

然后是位置。

需要申明的是,这个程序并不是一般的2048游戏,但是也符合2048的规则,

这个程序的唯一作用就是用来构造数据

所以,这个程序不需要随机算法来决定在哪个格子出新数。

只需要按照s[1],s[2],s[3]......s[16]的顺序依次查看哪里有空格,第一个发现的空格作为出新数的地方即可。

根据我玩2048的经验,这样应该是可以玩到最高分的,这一点我并没有去验证,因为这样的话构造数据的时间代价比较高,我引入了flag这个变量,这是在整个构造数据的过程中唯一需要修改代码的地方。

实际上,在构造整个数据的过程中,并不要求程序一直是不变的,在需要的时候,随时都可以修改程序然后接着构造数据

(4)读取键盘

键盘的方向键和数字字母键不同,数字字母键每个按键对应1个字符,而方向键每个按键对应2个字符,根据后面那个字符可以判断是哪个方向

代码:

char direction()//读取键盘的方向键
{
	int c1 = _getch(), c2 = _getch();
	if (c2 == 72)return 'u';
	if (c2 == 80)return 'd';
	if (c2 == 75)return 'l';
	if (c2 == 77)return 'r';
	return ' ';
}

其他的代码细节就不再解释了。

(5)完整代码

#include<iostream>
#include <fstream>
#include<conio.h>
#include<string>
using namespace std;

void display(int *p)//显示游戏界面
{
	for (int i = 0; i < 4; i++)
	{
		printf("\n\n\n");
		for (int j = 0; j < 4; j++)printf("%8d", *(p + i * 4 + j + 1));
	}
	printf("\n\n\n");
}

char direction()//读取键盘的方向键
{
	int c1 = _getch(), c2 = _getch();
	if (c2 == 72)return 'u';
	if (c2 == 80)return 'd';
	if (c2 == 75)return 'l';
	if (c2 == 77)return 'r';
	return ' ';
}

int dif(char c)//每个方向的运动偏移
{
	if (c == 'u')return -4;
	if (c == 'd')return 4;
	if (c == 'l')return -1;
	if (c == 'r')return 1;
	return 0;
}

bool move(int *p, int dif)//p,p+d,p+2d,p+3d
{
	int a = *(p), b = *(p + dif), c = *(p + dif * 2), d = *(p + dif * 3); //往d的方向移动
	for (int i = 0; i < 3; i++)//先把0集中在起点处,然后就只有相邻的数才能合并了
	{
		if (d == 0)d = c, c = b, b = a, a = 0;
		if (c == 0)c = b, b = a, a = 0;
		if (b == 0)b = a, a = 0;
	}
	if (a == b && c == d)d *= 2, c = b * 2, b = 0, a = 0;
	else if (c == d)d *= 2, c = b, b = a, a = 0;
	else if (b == c)c *= 2, b = a, a = 0;
	else if (a == b)b *= 2, a = 0;
	//上面计算了移动之后的数值,下面和移动之前比较,看是否真的有移动
	if (*(p) == a && *(p + dif) == b && *(p + dif * 2) == c && *(p + dif * 3) == d)return false;
	*(p) = a, *(p + dif) = b, *(p + dif * 2) = c, *(p + dif * 3) = d;
	return true;
}

void move(int *p)//滑动
{
	int d = dif(direction());
	*(p + 17) = d;
	bool b = false;
	if (d == 4)for (int i = 1; i <= 4; i++)if (move(p + i, d))b = true;
	if (d == -4)for (int i = 13; i <= 16; i++)if (move(p + i, d))b = true;
	if (d == 1)for (int i = 1; i <= 13; i += 4)if (move(p + i, d))b = true;
	if (d == -1)for (int i = 4; i <= 16; i += 4)if (move(p + i, d))b = true;
	if (!b)move(p);
}

int newNum(int *p)
{
	int s = 0, k, num0 = 0;
	for (int i = 1; i <= 16; i++)
	{
		k = *(p + i);
		if ((s^k) < s)return 2;//非0数不重复,否则出2
		if ((s^k) == s)num0++;//统计空格数量
		if (s % 4)return 2;//没有2,否则出2
		s ^= k;
	}
	if (num0 == 1)return 4;
	return 2;
}

void new_num(int *p)//判断哪里可以出1个新数
{
	bool flag = false;//唯一可以修改的地方
	int sum = 0;
	for (int i = 1; i <= 16; i++)if (*(p + i) == 0)sum++;//统计空格数量
	if (sum < 2)flag = false;
	for (int i = 0; i < 4; i++)for (int j = 0; j < 4; j++)if (*(p + i * 4 + j + 1) == 0)
	{
		if (flag)
		{
			flag = false;
			continue;
		}
		*(p + 18) = i, *(p + 19) = j;
		*(p + 20) = newNum(p);
		*(p + i * 4 + j + 1) = *(p + 20);
		return;
	}
}

int main()
{
	system("COLOR F0");
	ifstream in("2048.txt");
	ofstream out("2.txt");
	string in_line;
	while (getline(in, in_line));//注意2048.txt最后一行不能是空行
	char line[200];
	for (int i = 0; i < in_line.length(); i++)line[i] = in_line[i];
	int s[21];
	sscanf(line, "%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d", s+1,s+2,s+3,s+4,s+5,s+6,s+7,s+8,s+9,s+10,s+11,s+12,s+13,s+14,s+15,s+16,s+17,s+18,s+19,s+20);
	while (true)
	{
		display(s);
		move(s);
		new_num(s);
		for (int i = 1; i <= 20; i++)out << *(s + i) << ' ';
		out << endl;
	}
	return 0;
}

有了代码之后就可以开始构造数据了,在这个cpp的同一目录下新建2048.txt,写入第一行

0 0 0 0 0 0 0 0 2 0 0 0 2 0 0 0 0 0 0 0

2.txt不需要新建,2048.txt只用来读,2.txt只用来写,每次程序停止运行后,手动把2.txt中的内容全选复制粘贴到2048.txt的最后面即可

在构造数据的过程中,我们很容易发现,在此代码(中途不改flag的情况下)的基础上,其实2048.txt中的大部分数据都是可以省略的,只需要记录玩家每一次的操作,再加上第一行数据,即可还原出所有数据,这样就便于传输,尤其是那种不能发文件只能发文字的聊天环境。

于是我们需要压缩代码和还原代码

(6)压缩代码

#include<iostream>
#include <fstream>
#include<string>
using namespace std;

int main()
{
	ifstream in("2048.txt");
	ofstream out("1.txt");
	string in_line;
	char line[200];
	int s[21];
	getline(in, in_line);
	out << in_line<<'\n';
	while (getline(in, in_line))
	{
		for (int i = 0; i < in_line.length(); i++)line[i] = in_line[i];
		sscanf(line, "%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d", s + 1, s + 2, s + 3, s + 4, s + 5, s + 6, s + 7, s + 8, s + 9, s + 10, s + 11, s + 12, s + 13, s + 14, s + 15, s + 16, s + 17, s + 18, s + 19, s + 20);
		out << *(s + 17) << ' ';
	}
	return 0;
}

(7)还原代码

#include<iostream>
#include <fstream>
using namespace std;

int direc;

void move(int *p)//滑动
{
	int d = direc;
	*(p + 17) = d;
	bool b = false;
	if (d == 4)for (int i = 1; i <= 4; i++)if (move(p + i, d))b = true;
	if (d == -4)for (int i = 13; i <= 16; i++)if (move(p + i, d))b = true;
	if (d == 1)for (int i = 1; i <= 13; i += 4)if (move(p + i, d))b = true;
	if (d == -1)for (int i = 4; i <= 16; i += 4)if (move(p + i, d))b = true;
	if (!b)move(p);
}

int main()
{
	FILE *fp = fopen("1.txt", "r");
	ofstream out("3.txt");
	int s[21];
	fscanf(fp, "%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d", s + 1, s + 2, s + 3, s + 4, s + 5, s + 6, s + 7, s + 8, s + 9, s + 10, s + 11, s + 12, s + 13, s + 14, s + 15, s + 16, s + 17, s + 18, s + 19, s + 20);
	int count = 0;
	while (fscanf(fp,"%d",&direc)!=-1)
	{
		count++;
		move(s);
		new_num(s);
		for (int i = 1; i <= 20; i++)out << *(s + i) << ' ';
		out << endl;
	}
	return 0;
}

为了验证代码的正确性,可以先压缩再还原,然后用文本比较工具比较2个txt是否完全相同

经过比较,除了少数行尾有没有空格不一样之外,完全一样

2048.txt完整数据:和2048安装包放一起,在上面的分享链接中

3,验证数据正确性

只需要一个程序,逐行读取2048.txt中的文件,判断每一行到下一行的转化是正确的即可。

代码:

#include<iostream>
#include <fstream>
#include<string>
using namespace std;

bool move(int *p, int dif)//p,p+d,p+2d,p+3d
{
	int a = *(p), b = *(p + dif), c = *(p + dif * 2), d = *(p + dif * 3); //往d的方向移动
	for (int i = 0; i < 3; i++)//先把0集中在起点处,然后就只有相邻的数才能合并了
	{
		if (d == 0)d = c, c = b, b = a, a = 0;
		if (c == 0)c = b, b = a, a = 0;
		if (b == 0)b = a, a = 0;
	}
	if (a == b && c == d)d *= 2, c = b * 2, b = 0, a = 0;
	else if (c == d)d *= 2, c = b, b = a, a = 0;
	else if (b == c)c *= 2, b = a, a = 0;
	else if (a == b)b *= 2, a = 0;
	//上面计算了移动之后的数值,下面和移动之前比较,看是否真的有移动
	if (*(p) == a && *(p + dif) == b && *(p + dif * 2) == c && *(p + dif * 3) == d)return false;
	*(p) = a, *(p + dif) = b, *(p + dif * 2) = c, *(p + dif * 3) = d;
	return true;
}

bool move(int *p)//滑动
{
	int d = *(p + 17) ;
	bool b = false;
	if (d == 4)for (int i = 1; i <= 4; i++)if (move(p + i, d))b = true;
	if (d == -4)for (int i = 13; i <= 16; i++)if (move(p + i, d))b = true;
	if (d == 1)for (int i = 1; i <= 13; i += 4)if (move(p + i, d))b = true;
	if (d == -1)for (int i = 4; i <= 16; i += 4)if (move(p + i, d))b = true;
	return true;
}

bool ok(int *s0, int *s)
{
	for (int i = 17; i <= 20; i++)s0[i] = s[i];
	if (move(s0))
	{
		int i = *(s0 + 18), j = *(s0 + 19);
		if (*(s0 + i * 4 + j + 1))return false;
		*(s0 + i * 4 + j + 1) = *(s0+ 20);
		for (int i = 1; i <= 16; i++)if (s0[i] != s[i])return false;
		return true;
	}
	return false;
}

int main()
{
	ifstream in("2048.txt");
	string in_line;
	char line[200];
	int s0[21], s[21];
	getline(in, in_line);
	for (int i = 0; i < in_line.length(); i++)line[i] = in_line[i];
	sscanf(line, "%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d", s + 1, s + 2, s + 3, s + 4, s + 5, s + 6, s + 7, s + 8, s + 9, s + 10, s + 11, s + 12, s + 13, s + 14, s + 15, s + 16, s + 17, s + 18, s + 19, s + 20);
	for (int i = 1; i <= 16; i++)s0[i] = s[i];
	while (getline(in, in_line))
	{
		for (int i = 0; i < in_line.length(); i++)line[i] = in_line[i];
		sscanf(line, "%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d", s + 1, s + 2, s + 3, s + 4, s + 5, s + 6, s + 7, s + 8, s + 9, s + 10, s + 11, s + 12, s + 13, s + 14, s + 15, s + 16, s + 17, s + 18, s + 19, s + 20);
		if (ok(s0, s))for (int i = 1; i <= 16; i++)s0[i] = s[i];
		else
		{
			for (int i = 1; i <= 20; i++)cout << *(s + i) << ' ';
			break;
		}
	}	
	return 0;
}

4,计算得分

代码:

#include<iostream>
#include <fstream>
#include<string>
using namespace std;

int main()
{
	ifstream in("2048.txt");
	string in_line;
	char line[200];
	int s[21];
	while (getline(in, in_line))
	{
		for (int i = 0; i < in_line.length(); i++)line[i] = in_line[i];
		sscanf(line, "%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d", s + 1, s + 2, s + 3, s + 4, s + 5, s + 6, s + 7, s + 8, s + 9, s + 10, s + 11, s + 12, s + 13, s + 14, s + 15, s + 16, s + 17, s + 18, s + 19, s + 20);
		if (*(s + 17) == -4)cout << "上滑操作:"<<line;
		if (*(s + 20) == 4)
		{
			for (int i = 1; i <= 20; i++)cout << *(s + i) << ' ';
			cout << endl;
		}
	}
	return 0;
}

运行结果表明,一共只出现了16个4,所以理论最高分3932100是可以达到的。

运行结果还表明,从头到尾只有左右下3种操作,没有向上滑的操作。

六,2048格子位置判定

最近发现2048可以自定义:

只看下面的16个格子,不看上面的统计数据。

看到这么多格子,首先很明显,至少有1个是2,而且至少有1个是4

可能全部由2和4组成,也可能由2和4和8组成,等等。

总之,这样就有个问题,因为分不清楚不同的数字,所以很难玩下去。

下面考虑这个问题:

如果2、4、8、16等等,所有的格子都是一模一样的,那么2048会有多少种看起来不一样的局面?

根据时间,可以分为3类。

第一类:刚开始的时候,有2个格子。

第二类:某次滑动之后,系统暂时还没添加2

第三类:系统添加2之后,玩家还没滑动

这只是根据时间来分而已,它们所包含的局面互相都各有重合的,所以考虑重合的问题是本文的关键。

虽然第一类比较简单,但是第二类更规则,应该从第二类开始分析。

第二类满足一个重要的规则:所有的格子都靠着某条边。

比如说,如果最后一次滑动是向下滑,那么所有的格子都是靠着下面的。

这样,每一列都有5种情况,即这一列的格子数目是0、1、2、3、4

这样,一共是5*5*5*5=625种情况。

但是,第三类就非常复杂了。

一方面,最新添加的2的位置不好分析,另外一方面,存在既靠着下面又靠着左边的情况,这样讨论就十分复杂了。

解决这种讨论十分繁琐的问题的途径之一,自然是编程了。

首先,信息的表示。

用1个16位二进制整数代表一个局面,从上到下,从左往右,比如上面的图片就是0000 0010 0011 1111

然后定义合法的局面。

合法的局面有2种,一种是刚开始的时候有2个格子,即第一类时间,

还有一种是第二类时间和第三类时间的合并,即要么全部靠着某条边,要么除了某个格子之外全部靠着某条边。

(没有格子是不算靠着边的)

除此之外的局面自然是非法的。

但是要说这些局面就一定都是合法的,还要看2048的规则。

实际上,只要是满足上述条件就一定是合法的,因为存在仅由2、4、8、16构成的对应的局面,详情略。

因为总局面只有2^16=65536种,所以直接枚举就可以了,不需要用计数的方法。

虽然4个方向是等价的,但是现在有了确定的编码方案,4个方向就不一样了,最容易判断的,是是否靠右边。

靠右边就是4行都靠右边,而一行里面,靠右边就只有5种情况:0000、0001、0011、0111、1111

判断一行的话,只需要一个简单的函数ok就行

bool ok(int n)
{
	if (n != 0 && n != 1 && n != 3 && n != 7 && n != 15)return false;
	return true;
}

判断一个局面是否靠右,只需要4次调用ok函数即可

bool right(int n)
{
return ok(n & 15) && ok((n >> 4) & 15) && ok((n >> 8) & 15) && ok((n >> 12) & 15);
}

判断一个局面是否靠左,可以转化成是否靠右的问题。

总局面有65536种,即从0到65535,这些局面可以两两配对。

i+j=65535的2个局面i和j即为配对的局面,这样的2个局面是互补的。

这样,是否靠左的问题就可以转化成是否靠右的问题了。

所以我们可以得到,判断是否靠左或者靠右的函数left_or_right:

bool left_or_right(int n)
{
	if (right(n))return true;
	return right(65535 - n);
}

然后判断是否靠上或者靠下,这个问题可以转化成是否靠左或者靠右。

类似上面的做法,这里同样需要一种变换,不过不像上面的互补这么简洁,这里的变换是沿着对角线翻转。

所以,这样我们就得到了判断一个局面是否靠着某条边的函数all_direction:

bool all_direction(int n)
{
	if (n == 0)return false;
	if (left_or_right(n))return true;
	return left_or_right(n - (((n>>4)%2 - (n>>1)%2) * 14 + ((n>>8)%2 - (n>>2)%2) * 252 + ((n>>9)%2 - (n>>6)%2) * 448 + ((n>>12)%2 - (n>>3)%2) * 4088 + ((n>>13)%2 - (n>>7)%2) * 8064 + ((n>>14)%2 - (n>>11)%2) * 14336));
}

其中专门把n=0的情况排除掉了。

现在可以在主函数里面统计第二种合法局面的个数了。

int main()
{	
    int r[65536];
	for (int i = 0; i < 65536; i++)
	{
		r[i] = 0;
		if (all_direction(i))r[i] = 1;
		for (int k = 0; k < 16; k++)if ((i&(1<<k))!=0)
		if (all_direction(i^(1<<k)))r[i] = 1;
	}
	int result = 0;
	for (int i = 0; i < 65536; i++)result += r[i];
	cout << result;
	return 0;
}


 

结果是11755

除此之外,还有第一种合法局面,即刚开始的2个格子。

任选2个格子,得到的局面都是第一种合法局面,但是这2种情况有重合的部分,只需要考虑,不在这1175种情况之中的情况。

即2个格子是不靠边的,去掉其中一个格子之后,另外一个格子也是不靠边的。

这样,2个格子都不能靠边,即只能在中间的4个格子里面选取2个不同的格子,所以一共有6种情况。

所以最后的结果是11755+6=11761

  • 13
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
单词搜索迷宫(Word Search Puzzle)问题是一个经典的算法问题,其输入是一个二维的字符数组和一组单词,目标是找出字符数组网格中的所有单词。这些单词可以是水平的、垂直的或者是任意的对角线方向,所以需要查找8个不同的方向。解决这个问题的一种常见方法是使用回溯算法,具体步骤如下: 1. 遍历二维字符数组,对于每个字符,以其为起点开始搜索,搜索的方向包括水平、垂直和对角线方向。 2. 对于每个搜索到的单词,将其记录下来。 3. 重复步骤1和2,直到遍历完整个二维字符数组。 下面是一个使用C#语言实现的单词搜索迷宫算法的示例代码: ```csharp class WordSearchPuzzle { private char[,] grid; private HashSet<string> words; public WordSearchPuzzle(char[,] grid, HashSet<string> words) { this.grid = grid; this.words = words; } public void Solve() { int rows = grid.GetLength(0); int cols = grid.GetLength(1); for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { Search(i, j, new StringBuilder()); } } } private void Search(int row, int col, StringBuilder sb) { if (row < 0 || row >= grid.GetLength(0) || col < 0 || col >= grid.GetLength(1)) { return; } sb.Append(grid[row, col]); string word = sb.ToString(); if (words.Contains(word)) { Console.WriteLine("Found '{0}' at [{1}, {2}] to [{3}, {4}]", word, row, col, row - sb.Length + 1, col - sb.Length + 1); } if (word.Length < 3) { Search(row + 1, col, sb); Search(row - 1, col, sb); Search(row, col + 1, sb); Search(row, col - 1, sb); Search(row + 1, col + 1, sb); Search(row - 1, col - 1, sb); Search(row + 1, col - 1, sb); Search(row - 1, col + 1, sb); } sb.Remove(sb.Length - 1, 1); } } // 使用示例 char[,] grid = new char[,] { {'t', 'h', 'i', 's'}, {'w', 'a', 't', 's'}, {'o', 'a', 'h', 'g'}, {'f', 'g', 'd', 't'} }; HashSet<string> words = new HashSet<string>() { "this", "two", "fat", "that" }; WordSearchPuzzle puzzle = new WordSearchPuzzle(grid, words); puzzle.Solve(); ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值