目录
欧德里安
(2)
(3)
(4)
(5)
(9)
(18)
(23)
燃烧的画
给定初始状态,经过一系列加热和旋转操作,变成目标状态。
加热规则:数字是1则不动,数字x大于1则拆分成1和x-1,把1留在原地,x-1流到下面的一个格子。
4*4
不同的初始态和目标态,也有不同的难度。
4*4里面简单的:
4*4里面复杂的:
6*6
8*8(策略)
开局选方向
在第一次加热之前,我们就需要把方向确定下来。
选方向可以利用的特征很多,比较容易。
比如这一局,我们利用左上角的局部特征:
方向对齐之后是这样:
从最难达成的颜色开始逆推
目前来看,最难达成的是这2个颜色:
这2个颜色基本上要走最短路,不能浪费,所以可以反推出大概的操作方向。
10*10
12*12
这一关是最强大脑原题。
首先确定方向,就是当前方向。
然后选择最难达成的颜色,即下面的棕色和绿色:
所以可选择的方案并不多。
到这一步,最后一行就做完了。
后面就比较简单,策略也还是差不多。
移箱迷宫
规则:
补充规则:
每次滑动,相当于是盒子和四邻居之一交换位置,这个邻居可能是空格也可能是盒子,交换位置之后所有格子往下掉落。
如果形成3个以上,也会一次性全部消除。
如果多个方向同时形成3个或以上,也会一次性全部消除。
消除之后,剩下的盒子会往下掉落,如果形成新的可消除组合,则自动继续消除。
初级
(11)
首先寻找匹配方向:
然后寻找优先消除块:
最后找到操作方法:
(15)
首先寻找匹配方向:
然后寻找优先消除块:
我画了3个黑框,因为左边2个块和上边1个块距离太远,所以在消除这3个块之前还要先把右边的6个块消除掉。
最后找到操作方法:
(23)
这里的匹配结果是蓝色格子往左移2步,所以操作方法就是:
(24)
(27)
(30)
(33)
(39)
(40)
(42)
首先,红色和深蓝色的水平坐标是OK的,即只要往下掉到同一行即可消除,而黄色是左边的格子需要往右移动一步。
基于此,比较容易想到一个三步的操作方法:
根据这个方案,可以想到一个步骤更少的方案:
(45)
参考下文《递归搜索》
(49)
策略一:纵向匹配法
首先寻找匹配方向,然后寻找优先消除块,最后找到操作方法。
参考初级关卡(11)和(15)
策略二:横向匹配法
如果寻找到的匹配方向是把某个单个的箱子横着移动,则直接得到对应数量的操作内容。
参考初级关卡(23)
策略三:基于移动方格的方案简化
有时我们拿到一个操作数比较多的方案,可以分析我们的整体操作是把哪些方格移动了,移到了什么位置,基于这个操作结果可以反推出一个操作数更少的操作方案。
参考初级关卡(42)
递归搜索
我们把不同的颜色用正整数区分,0表示空格,然后用递归搜索即可搜出一个解。
注意,我们在框定尺寸时,要往左右两边留出空列,而最上面是不需要留出空行的。
代码:
bool down(vector<vector<int>>&v)
{
bool flag = false;
int row = v.size(), col = v[0].size();
for (int j = 0; j < col; j++) {
int low = 0;
for (int i = 1; i < row; i++) {
if (v[low][j] == 0) {
if (v[i][j])low = i;
}
else {
if (v[i][j])continue;
for (int k = i; k>low; k--) {
v[k][j] = v[k - 1][j];
}
v[low][j] = 0;
flag = true;
low++;
}
}
}
return flag;
}
bool dels(vector<vector<int>>& v)
{
int row = v.size(), col = v[0].size();
vector<vector<bool>>canDel(row, vector<bool>(col, false));
for (int i = 0; i < row; i++) {
for (int j = 2; j < col; j++) {
if (v[i][j] && v[i][j - 2] == v[i][j] && v[i][j - 1] == v[i][j]) {
canDel[i][j - 2] = canDel[i][j - 1] = canDel[i][j] = true;
}
}
}
for (int i = 2; i < row; i++) {
for (int j = 0; j < col; j++) {
if (v[i][j] && v[i-2][j] == v[i][j] && v[i-1][j] == v[i][j]) {
canDel[i-2][j] = canDel[i-1][j] = canDel[i][j] = true;
}
}
}
bool flag = false;
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
if (canDel[i][j]) {
flag = true, v[i][j] = 0;
}
}
}
return flag;
}
void downAndDel(vector<vector<int>>& v)
{
while (down(v) || dels(v));
}
bool isFinish(const vector<vector<int>>& v)
{
int row = v.size(), col = v[0].size();
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
if (v[i][j]) return false;
}
}
return true;
}
bool search(vector<vector<int>> v, int n)
{
if (!n)return isFinish(v);
int row = v.size(), col = v[0].size();
for (int i = 0; i < row; i++) {
for (int j = 1; j < col; j++) {
if (v[i][j] == v[i][j - 1])continue;
auto v2 = v;
v[i][j] ^= v[i][j - 1] ^= v[i][j] ^= v[i][j - 1];
downAndDel(v);
if (search(v, n-1)) {
cout << i << " " << j << " " << i << " " << j - 1 << endl;
return true;
}
v = v2;
}
}
for (int i = 1; i < row; i++) {
for (int j = 0; j < col; j++) {
if (v[i][j] == v[i-1][j])continue;
auto v2 = v;
v[i][j] ^= v[i - 1][j] ^= v[i][j] ^= v[i - 1][j];
downAndDel(v);
if (search(v, n - 1)) {
cout << i << " " << j << " " << i-1 << " " << j << endl;
return true;
}
v = v2;
}
}
return false;
}
以初级关卡(45)为例:
所以调用代码就是:
int main()
{
vector<vector<int>>v{
{0,1,0,0,0,0},
{0,2,0,0,0,0},
{0,3,0,0,0,0},
{0,2,1,0,0,0},
{0,1,3,3,0,0},
{0,2,2,1,0,0},
{0,1,1,2,2,0}
};
search(v, 2);
return 0;
}
运行结果:
4 1 4 0
3 1 2 1
从下往上,先把(3,1)和(2,1)互换,再把(4,1)和(4,0)互换
即:
策略四:难以分割的相邻同色块
在上面的初级关卡(45)中,其实很容易想到红色的块往左推的操作,但是如果第一步就这样操作就会造成四个黄色块(最下面一行2个,倒数第二行2个)一起消除,从而无解。
我的思路一直是如何分割倒数第二行的2个黄色块,一直没想过可以让这6个黄色块同时消除。
所以策略四就是,如果遇到难以分割的相邻同色块,考虑不分割,而是两组同时消除。
中级
(2)
这一关的关键是4个黄的要一起消除,4个深蓝的也要一起消除。