斜转魔方、斜转扭曲魔方

目录

斜转魔方

1,魔方三要素

(1)组成部件

(2)可执行操作

(3)目标态

 2,复原方法

(1)调整8个角块位置

(2)调整6个中心块位置

(3)调整角块方向

3,中心块位置的奇偶性

4,状态空间

5,计算机求解

(1)状态空间大小

(2)最大所需操作数

(3)BFS求解

斜转扭曲魔方

1,魔方三要素

2,中心块朝向奇偶性

3,复原方法

(1)调整8个角块位置

(2)调整6个中心块位置

(3)调整角块方向

(4)调整中心块朝向


斜转魔方

1,魔方三要素

(1)组成部件

8个角块和6个中心块

 把8个角块编号12345678,分别对应上层的左上、右上、左下、右下以及下层的。

(2)可执行操作

斜转魔方既可以理解成四轴魔方,也可以理解成八轴魔方。

如果认为是8种操作的话,那么可以分成四组,任意一组的2个操作各执行1次或2次加起来都相当于魔方整体旋转。

按照4种操作理解的话,每种操作都会改变3个角块的位置,另外5个角块位置不变,这4个操作影响的3个角块分别是:167、258、358、467

(3)目标态

(3.1)形状

任意一次操作之后,魔方都是正方体,最后也自然是正方体。

(3.2)颜色

8个角块可以唯一确定6个面的位置关系。

 2,复原方法

(1)调整8个角块位置

先调整4个角块的位置,此时可能出现的情况,可以用bfs算一下:

string f1(string s)
{
	char c = s[3];
	s[3] = s[6], s[6] = s[5], s[5] = c;
	return s;
}
string f2(string s)
{
	char c = s[2];
	s[2] = s[4], s[4] = s[7], s[7] = c;
	return s;
}
string f3(string s)
{
	char c = s[0];
	s[0] = s[6], s[6] = s[5], s[5] = c;
	return s;
}
string f4(string s)
{
	char c = s[1];
	s[1] = s[4], s[4] = s[7], s[7] = c;
	return s;
}

void bfs()
{
	string s = "12345678";
	queue<string>q;
	q.push(s);
	map<string, int>m;
	m[s] = 1;
	while (!q.empty())
	{
		string str = q.front();
		q.pop();
		string s = f1(str);
		if (!m[s])m[s] = 1, q.push(s);
		s = f2(str);
		if (!m[s])m[s] = 1, q.push(s);
		s = f3(str);
		if (!m[s])m[s] = 1, q.push(s);
		s = f4(str);
		if (!m[s])m[s] = 1, q.push(s);
	}
	for (auto mi : m) {
		bool flag = true;
		for (int i = 0; i < 4; i++)if (mi.first[i]>'4')flag=false;
		if(flag)cout << mi.first << endl;
	}
}

输出:

12345678
13248675
42315768
43218765

所以,只需要上层是1234,下层就一定是5678

这一步非常简单,不需要任何公式。

(2)调整6个中心块位置

以8、5、3为轴顺时针各一次,8个角块的位置不变,左下中心块互换,另外4个中心块轮换。

为了方便,我们把这个叫做853操作。

把6个中心块编号为1上2下3前4后5左6右,则853操作对中心块的位置影响是123456->451623

另外3个操作(674、582、761)的影响依次是532641、365142、645213

于是我们可以就可以推导中心块调整位置的公式:

string f1(string s)
{
	char c = s[2];
	s[2] = s[5], s[5] = c;
	c = s[1];
	s[1] = s[4], s[4] = s[6], s[6] = s[3], s[3] = c;
	return s;
}
string f2(string s)
{
	char c = s[2];
	s[2] = s[3], s[3] = c;
	c = s[1];
	s[1] = s[5], s[5] = s[4], s[4] = s[6], s[6] = c;
	return s;
}
string f3(string s)
{
	char c = s[2];
	s[2] = s[6], s[6] = c;
	c = s[1];
	s[1] = s[3], s[3] = s[5], s[5] = s[4], s[4] = c;
	return s;
}
string f4(string s)
{
	char c = s[2];
	s[2] = s[4], s[4] = c;
	c = s[1];
	s[1] = s[6], s[6] = s[3], s[3] = s[5], s[5] = c;
	return s;
}

void bfs()
{
	string s = " 123456";
	queue<string>q;
	q.push(s);
	map<string, int>m;
	m[s] = 1;
	while (!q.empty())
	{
		string str = q.front();
		q.pop();
		string s = f1(str);
		if (!m[s])m[s] = 1, q.push(s);
		s = f2(str);
		if (!m[s])m[s] = 1, q.push(s);
		s = f3(str);
		if (!m[s])m[s] = 1, q.push(s);
		s = f4(str);
		if (!m[s])m[s] = 1, q.push(s);
	}
	for (auto mi : m) {
		int x = 0;
		for (int i = 0; i < 7; i++)if (mi.first[i]==s[i])x++;
		if(x>=4)cout << mi.first << endl;
	}
}

输出:

 123456
 123564
 123645
 124536
 124653
 125346
 125463
 126354
 126435
 134256
 135426
 136452
 142356
 143526
 143652
 152436
 153246
 153462
 162453
 163254
 163425
 231456
 243156
 253416
 263451
 312456
 324156
 325416
 326451
 413256
 421356
 423516
 423651
 513426
 521436
 523146
 523461
 613452
 621453
 623154
 623415

理论上任选一个都可以作为基础公式模式。

为了方便,我们选143526作为公式模式。

代码略改一下,求出123456到143526的变换路径:

void bfs()
{
	string s = " 123456";
	queue<string>q;
	q.push(s);
	map<string, int>m;
	map<string, string>p;
	map<string, int>p2;
	m[s] = 1;
	while (!q.empty())
	{
		string str = q.front();
		q.pop();
		string s = f1(str);
		if (!m[s])m[s] = 1, q.push(s), p[s] = str, p2[s] = 1;
		s = f2(str);
		if (!m[s])m[s] = 1, q.push(s), p[s] = str, p2[s] = 2;
		s = f3(str);
		if (!m[s])m[s] = 1, q.push(s), p[s] = str, p2[s] = 3;
		s = f4(str);
		if (!m[s])m[s] = 1, q.push(s), p[s] = str, p2[s] = 4;
	}
	s = " 143526";
	while (s != " 123456") {
		cout << s<<"  "<<p2[s] << "  ";
		s = p[s];
	}
	cout << s << endl;
}

输出:

 143526  2   634215  1   415632  3   624351  1   451623  1   123456

也就是说,从123456,依次执行853、853、582、853 、674操作,共15次操作,即可变成143526,即245三个色块的轮换。

有此公式,即可完成所有中心块的归位。

亲测有效:

会不会出现只有2个面的颜色反过来的情况呢?不会

首先,如果会出现只有2个面的颜色反过来的情况,那就一定会出现123456变成123546,即012345变成012435,且一定是只需要4个操作的组合就可以达成。(这一步的推理已经考虑了整个魔方旋转的场景)

其次,用程序枚举:

int main()
{
	CubeBlock block1(0, 6);//6中心块
	vector<CubeBlock>b = vector<CubeBlock>{ block1 };
	Opt opt1{ { {3,1,2,4,0,5}} ,"左上" };
	Opt opt2{ { {3,1,2,5,4,0} } ,"右上" };
	Opt opt3{ { {2,1,4,3,0,5} } ,"左下" };
	Opt opt4{ { {2,1,5,3,4,0} } ,"右下" };
	CubeOpt op1(b, opt1);
	CubeOpt op2(b, opt2);
	CubeOpt op3(b, opt3);
	CubeOpt op4(b, opt4);
	vector<CubeOpt>opts = { op1,op2,op3,op4 };
	for (int i = 0; i < opts.size(); i++)mans[i] = opts[i].getName();
	Cube cube(b, opts);
	cube.bfs(0, 1, 2);
	return 0;
}

输出:k=60

果然不存在这种场景

(3)调整角块方向

把853操作直接重复4遍,则所有块位置不变,角块中24方向不变,其他6个角块都顺时针转一次。

我们把它理解成对8个角块顺时针旋转,并对24逆时针旋转,把这个操作叫做24操作。

2个12操作+1个24操作,我们就得到一个只改变1和4不改变另外6个角块的操作(1顺时针旋转4逆时针旋转)。

有了这个操作,显然可以把除了2个相邻角块之外的所有角块方向复原。

最后,有没有可能只有2个相邻角块方向不对呢?应该不可能。

至此,魔方复原

3,中心块位置的奇偶性

我们会遇到一个状态,4个面的中心块的位置都是好的,只有2个是反的。

那是不是可以理解成任意2个中心块的位置都可以互换呢?

不能这么简单的理解。

比如,白色是顶面,红色是前面,而后面和右面是反的,如果顶面白色永远保持不动,那么无论怎么操作,都不可能6个中心块归位,保持白色是上面,红色是前面。

因为,要想六个面的相对位置恢复,白色当然还是顶面,但前面一定是绿色或者蓝色。

“前面是绿色或者蓝色”这样的魔方的整体朝向是不符合标准色的,出现这个情况是因为在开始复原之前魔方整体旋转了90度。

换句话说,以我的这个魔方为例,只要复原了,顶面白色的“奇艺”两个字就一定不是正对着自己的,而是转了90度。

4,状态空间

我们都知道,三阶魔方的状态空间大小是8!* 3^8 * 12!*2^12 / 12 = 4325亿亿,这个挺大的一个数字,但即使这样,三阶魔方也只需要十几次操作就能复原。

同样的,对斜转魔方整体旋转的情况进行去重,我们只需要按照四轴的方式去理解即可。

按照4种操作理解的话,每种操作都会改变3个角块的位置,另外5个角块位置不变,这4个操作影响的3个角块分别是:167、258、358、467,也就是说,一开始上面是白色地面是黄色,则上面永远是黄色。

6个中心块的情况数是5!/2=60

8个角块的位置的情况数是4*3*4*3=144

8个角块的朝向的情况数是3^6=729

所以一共60*144*729=6298560种情况,连一千万都不到。

5,计算机求解

既然状态空间这么小,那就可以直接求出任意状态下的最优复原路线。

(1)状态空间大小

首先编码求一下状态空间的大小:


//把6个中心块索引为0上1下 2前3后 4左5右
//上层的左上、右上、左下、右下4个角块,索引是6、8、10、12
//下层的左上、右上、左下、右下4个角块,索引是14、16、18、20
//每个角块跟着一个朝向,复原状态的朝向都是0,即白色黄色都在上面和下面,朝向表示已经转了几格,3格消除

#define TURN1(a,b,c) {int tmp = v[a];v[a] = v[b], v[b] = v[c], v[c] = tmp; } //3个块位置轮换
#define TURN2(a) {v[a+1]=(v[a+1]+1)%3;} //1个角块旋转
#define TURN3(a,b,c) {TURN1(a,b,c);TURN1(a+1,b+1,c+1);TURN2(a);TURN2(b);TURN2(c);TURN2(a);TURN2(b);TURN2(c);} //3个角块位置轮换+朝向改变

//以下是4个顺时针旋转操作
vector<int> f1(vector<int>&input) 
{
	vector<int> v = input;
	TURN1(2, 1, 5);
	TURN2(20);
	TURN3(12, 18, 16);
	return v;
}
vector<int> f2(vector<int>&input)
{
	vector<int> v = input;
	TURN1(2, 4, 1);
	TURN2(18);
	TURN3(10, 14, 20);
	return v;
}
vector<int> f3(vector<int>&input)
{
	vector<int> v = input;
	TURN1(4, 3, 1);
	TURN2(14);
	TURN3(6, 16, 18);
	return v;
}
vector<int> f4(vector<int>&input)
{
	vector<int> v = input;
	TURN1(5,1,3);
	TURN2(16);
	TURN3(8, 20, 14);
	return v;
}

vector<std::function<vector<int>(vector<int>&)>> vf{ f1,f2,f3,f4 };

void bfs()
{
	vector<int>v{ 1,2,3,4,5,6,1,0,2,0,3,0,4,0,5,0,6,0,7,0,8,0 };
	queue<vector<int>>q;
	q.push(v);
	set<vector<int>>s;
	s.insert(v);
	while (!q.empty()) {
		auto v = q.front();
		q.pop();
		for (auto f : vf) {
			auto v2 = f(v);
			if (s.find(v2) == s.end()) {
				q.push(v2);
				s.insert(v2);
				//if (s.size() % 100000 == 0)cout << q.size() << " " << s.size() << endl;
			}
		}
	}
	cout << s.size();
}

int main()
{
	bfs();
	return 0;
}

输出:6298560

(2)最大所需操作数

把集合set改造成映射表map即可


//把6个中心块索引为0上1下 2前3后 4左5右
//上层的左上、右上、左下、右下4个角块,索引是6、8、10、12
//下层的左上、右上、左下、右下4个角块,索引是14、16、18、20
//每个角块跟着一个朝向,复原状态的朝向都是0,即白色黄色都在上面和下面,朝向表示已经转了几格,3格消除

#define TURN1(a,b,c) {int tmp = v[a];v[a] = v[b], v[b] = v[c], v[c] = tmp; } //3个块位置轮换
#define TURN2(a) {v[a+1]=(v[a+1]+1)%3;} //1个角块旋转
#define TURN3(a,b,c) {TURN1(a,b,c);TURN1(a+1,b+1,c+1);TURN2(a);TURN2(b);TURN2(c);TURN2(a);TURN2(b);TURN2(c);} //3个角块位置轮换+朝向改变

//以下是4个顺时针旋转操作
vector<int> f1(vector<int>&input) 
{
	vector<int> v = input;
	TURN1(2, 1, 5);
	TURN2(20);
	TURN3(12, 18, 16);
	return v;
}
vector<int> f2(vector<int>&input)
{
	vector<int> v = input;
	TURN1(2, 4, 1);
	TURN2(18);
	TURN3(10, 14, 20);
	return v;
}
vector<int> f3(vector<int>&input)
{
	vector<int> v = input;
	TURN1(4, 3, 1);
	TURN2(14);
	TURN3(6, 16, 18);
	return v;
}
vector<int> f4(vector<int>&input)
{
	vector<int> v = input;
	TURN1(5,1,3);
	TURN2(16);
	TURN3(8, 20, 14);
	return v;
}

vector<std::function<vector<int>(vector<int>&)>> vf{ f1,f2,f3,f4 };

void bfs()
{
	vector<int>v{ 1,2,3,4,5,6,1,0,2,0,3,0,4,0,5,0,6,0,7,0,8,0 };
	queue<vector<int>>q;
	q.push(v);
	map<vector<int>,int>s;
	s[v] = 0;
	while (!q.empty()) {
		auto v = q.front();
		q.pop();
		for (auto f : vf) {
			auto v2 = f(v);
			if (s.find(v2) == s.end()) {
				q.push(v2);
				s[v2] = s[v]+1;
				if (s.size() % 100000 == 0)cout << q.size() << " " << s.size() << endl;
				if (s.size() > 6298558)cout << s[v2] << endl;
			}
		}
	}
	cout << s.size();
}

int main()
{
	bfs();
	return 0;
}

输出16

也就是说,任意状态,最多需要16步就可以复原。

(3)BFS求解


//把6个中心块索引为0上1下 2前3后 4左5右
//上层的左上、右上、左下、右下4个角块,索引是6、8、10、12
//下层的左上、右上、左下、右下4个角块,索引是14、16、18、20
//每个角块跟着一个朝向,复原状态的朝向都是0,即白色黄色都在上面和下面,朝向表示已经顺时针转了几格,3格消除

#define TURN1(a,b,c) {int tmp = v[a];v[a] = v[b], v[b] = v[c], v[c] = tmp; } //3个块位置轮换
#define TURN2(a) {v[a+1]=(v[a+1]+1)%3;} //1个角块旋转
#define TURN3(a,b,c) {TURN1(a,b,c);TURN1(a+1,b+1,c+1);TURN2(a);TURN2(b);TURN2(c);TURN2(a);TURN2(b);TURN2(c);} //3个角块位置轮换+朝向改变

//以下是4个顺时针旋转操作
vector<int> f1(vector<int>& input)
{
	vector<int> v = input;
	TURN1(2, 1, 5);
	TURN2(20);
	TURN3(12, 18, 16);
	return v;
}
vector<int> f2(vector<int>& input)
{
	vector<int> v = input;
	TURN1(2, 4, 1);
	TURN2(18);
	TURN3(10, 14, 20);
	return v;
}
vector<int> f3(vector<int>& input)
{
	vector<int> v = input;
	TURN1(4, 3, 1);
	TURN2(14);
	TURN3(6, 16, 18);
	return v;
}
vector<int> f4(vector<int>& input)
{
	vector<int> v = input;
	TURN1(5, 1, 3);
	TURN2(16);
	TURN3(8, 20, 14);
	return v;
}

vector<std::function<vector<int>(vector<int>&)>> vf{ f1,f2,f3,f4 };

void bfs(vector<int>&target)
{
	vector<int>v{ 1,2,3,4,5,6,1,0,2,0,3,0,4,0,5,0,6,0,7,0,8,0 };
	queue<vector<int>>q;
	q.push(v);
	map<vector<int>, int>s;
	s[v] = 0;
	map<vector<int>, vector<int>>fa;	
	map<vector<int>, int>faOpt;
	faOpt[v] = -1;
	while (!q.empty()) {
		auto v = q.front();
		q.pop();
		for (int i = 0; i < 4;i++) {
			auto v2 = vf[i](v);
			if (s.find(v2) == s.end()) {
				q.push(v2);
				s[v2] = s[v] + 1;
				//if (s.size() % 100000 == 0)cout << q.size() << " " << s.size() << endl;
				fa[v2] = v;
				faOpt[v2] = i;
				if (v2 == target) {
					vector<string> strs{ "右下","左下","左上","右上" };
					int lineId = 0;
					while (faOpt[v2] >= 0) {
						cout <<++lineId<<"  以底层的" << strs[faOpt[v2]] << "的大角块为轴逆时针旋转" << endl;
						v2 = fa[v2];
					}
					return;
				}
			}
		}
	}
	cout << s.size();
}

//以下是4个顺时针旋转操作
vector<int> f5(vector<int>& input)
{
	vector<int> v = input;
	TURN1(2, 1, 5);
	return v;
}
vector<int> f6(vector<int>& input)
{
	vector<int> v = input;
	TURN1(2, 4, 1);
	return v;
}
vector<int> f7(vector<int>& input)
{
	vector<int> v = input;
	TURN1(4, 3, 1);
	return v;
}
vector<int> f8(vector<int>& input)
{
	vector<int> v = input;
	TURN1(5, 1, 3);
	return v;
}

vector<std::function<vector<int>(vector<int>&)>> vf2{ f5,f6,f7,f8 };

//奇偶性检查,即魔方整体要不要旋转90度
bool checkNeedTurn(vector<int>& target)
{
	vector<int>v{ 1,2,3,4,5,6};
	queue<vector<int>>q;
	q.push(v);
	map<vector<int>, int>s;
	s[v] = 0;
	while (!q.empty()) {
		auto v = q.front();
		q.pop();
		for (int i = 0; i < 4; i++) {
			auto v2 = vf2[i](v);
			if (s.find(v2) == s.end()) {
				q.push(v2);
				s[v2] = s[v] + 1;
			}
		}
	}
	if (s.find(target) != s.end()) {
		return false;
	}
	//篡改数据,把魔方整体往左旋转90度,保持白色放在顶面,也就是前面转到左边,左边转到后面
	int tmp = target[2];
	target[2] = target[5], target[5] = target[3];
	target[3] = target[4], target[4] = tmp;
	return true;
}

int main()
{
	vector<int>v{ 1,2,3,4,5,6};
	cout << "分别输入白色 黄色 红色 橙色 绿色 蓝色中心块的位置,用数字表示,0上1下 2前3后 4左5右" << endl;
	int id, t;
	for (int i = 1; i <= 6; i++) {
		cin >> id;
		v[id] = i;
	}
	if (v[0] != 1) {
		cout << "把白色放在顶面,重新运行程序";
		return 0;
	}
	if (checkNeedTurn(v)) {
		cout << "把魔方整体往左旋转90度,保持白色放在顶面,也就是前面转到左边,左边转到后面,然后继续" << endl;
	}

	for (int i = 1; i <= 16; i++) {
		v.push_back(0);
	}
	cout << "分别输入 白绿橙 白蓝橙 白绿红 白蓝红 黄绿橙 黄蓝橙 黄绿红 黄蓝红 8个大角块的位置和朝向" << endl;
	cout << "位置用数字表示,上层的左上、右上、左下、右下分别是6、8、10、12,下层的左上、右上、左下、右下分别是14、16、18、20" << endl;
	cout << "每个角块位置输入完立刻输入朝向,朝向表示已经顺时针转了几格,3格消除,0表示白色黄色都在上面和下面" << endl;
	for (int i = 1; i <= 8; i++) {
		cin >> id >> t;
		v[id] = i;
		v[id + 1] = t;
	}
	bfs(v);
	return 0;
}

运行示例:

亲测有效!

斜转扭曲魔方

1,魔方三要素

和斜转魔方唯一的区别就是,有4个中心块(蓝绿红橙)是扭曲的,也就是要区分朝向了。

2,中心块朝向奇偶性

我们把中心块旋转0度和180度叫偶状态,旋转90度和270度叫奇状态。

根据若干次尝试,我提出1个定理和1个猜想:

(1)定理:如果6个中心块的位置复原,无论角块的位置和朝向,6个中心块一定都是偶状态

用斜转魔方证明这个定理可能还不容易想出来,但是用斜转扭曲魔方证明就很容易了。

扭曲中心块是双色的,分界线是一条线段,延长之后刚好经过正方体的2个顶点。对顶点进行2染色,即可推导出中心块朝向的奇偶性。

(2)猜想:如果位置没有完全复原但是满足对立色,即6个中心块的位置是整个魔方成镜面,无论角块的位置和朝向,6个中心块一定都是奇状态。

这个猜想应该是对的,但是这里我不做证明。

基于(1),斜转扭曲魔方相对于斜转魔方,唯一需要多做的事情就是4个扭曲中心块中的若干个需要旋转180度。

3,复原方法

(1)调整8个角块位置

同斜转魔方

(2)调整6个中心块位置

同斜转魔方

(3)调整角块方向

同斜转魔方

因为4个扭曲中心块都是偶状态,所以形态是没问题的,只有颜色可能对不上。

(4)调整中心块朝向

上文有提到24操作,即把853操作直接重复4遍,则所有块位置不变,角块中24方向不变,其他6个角块都顺时针转一次。

我们定义一个242424操作,即853操作重复12遍,效果是所有块位置不变,所有角块朝向不变,25号中心块朝向不变,1346号中心块朝向旋转180度。

根据扭曲中心块需要调整朝向的数量,分为0 1 2 3 4这5种情况,4->1->2->3->0之间的转化都刚好是1个242424操作。

至此,魔方复原

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值