交换公式自动推导

目录

一,背景

二,实现思路

1,开放式的分组和编号方案

2,求解目标

三,V1代码(DFS)

四,V1+开放式尝试

五,V2代码(BFS)

六,V3代码(BFS)

七,V3+开放性尝试

八,V4代码(可视化优化)

九,搜索加速

十,V5代码(便于开放性尝试和加速搜索)

十一,全文总结

1,三阶魔方默认编号

2,代码使用总结

3,分组总结

4,搜索加速


一,背景

在第一次自己写代码推导魔方公式(四轴斜转魔方)之后,我决定写一个更通用的推导公式的代码。

输入:魔方的三要素,即部件、操作、目标

输出:操作序列

二,实现思路

1,开放式的分组和编号方案

分组方案需要最基本的魔方基础,比如角块为一组,棱块为一组,中心块为一组。

这些组分别编号第0组,第1组,第2组......具体怎么对应是开放的。

对于每一组,各个块的编号分别为0,1,2......具体怎么对应是开放的。

2,求解目标

求解目标一定是只有一组块交换位置,其他组的所有块位置不变。

三,V1代码(DFS)

为了方便打印路径,我采用了DFS

template<typename T>
class GetSingleId
{
public:
	int id(T x)
	{
		auto it = m.find(x);
		if (it != m.end())return it->second;
		return m[x] = n++;
	}
	int num()
	{
		return n;
	}
private:
	map<T, int>m;
	int n = 0;
};
template<typename T>
class GetCombineId
{
public:
	vector<int> combineId(vector<T>& x)
	{
		if (v.empty())v.resize(x.size());
		vector<int>ans(x.size());
		for (int i = 0; i < x.size(); i++)ans[i] = v[i].id(x[i]);
		return ans;
	}
	int id(vector<T>& x)
	{
		return v2.id(combineId(x));
	}
	int num()
	{
		return v2.num();
	}
	vector<GetSingleId<T>>v;
	GetSingleId<vector<int>>v2;
};
struct CubeBlock
{
	int typeId;//角块,棱块等,分组id
	vector<int>v;//一组块
	CubeBlock(int id, int n)
	{
		typeId = id;
		v.resize(n);
		for (int i = 0; i < n; i++)v[i] = i;//每一组的块都按从0开始编号
	}
	int changeNum()
	{
		int ans = 0;
		for (int i = 0; i < v.size(); i++)if (v[i] != i)ans++;
		return ans;
	}
	bool isOk()
	{
		return changeNum() == 0;
	}
	bool operator<(const CubeBlock& blocks)const
	{
		for (int i = 0; i < v.size() && i < blocks.v.size(); i++) {
			if (v[i] < blocks.v[i])return true;
			if (blocks.v[i] < v[i])return false;
		}
		return false;
	}
};
class CubeOpt
{
public:
	CubeOpt(vector<CubeBlock>& b, vector<vector<int>>& v) :b{ b }, v{ v }{}//若干组块及其变换
	CubeOpt& operator =(const CubeOpt& opt) {
		b = opt.b, v = opt.v;
		return *this;
	}
	void change()
	{
		for (int i = 0; i < v.size(); i++) {
			change(b[i], v[i]);
		}
	}
	void reback()
	{
		for (int i = 0; i < v.size(); i++) {
			reback(b[i], v[i]);
		}
	}
private:
	void change(CubeBlock& b, vector<int>& v)//一组块及一个变换,如v[1]=2表示把2号块移到1号块的位置
	{
		vector<int>bv = b.v;
		for (int i = 0; i < v.size(); i++) {
			b.v[i] = bv[v[i]];
		}
	}
	void reback(CubeBlock& b, vector<int>& v)
	{
		vector<int>bv = b.v;
		for (int i = 0; i < v.size(); i++) {
			b.v[v[i]] = bv[i];
		}
	}
	vector<vector<int>>& v;
	vector<CubeBlock>& b;
};

class Cube
{
public:
	Cube(vector<CubeBlock>& b, vector<CubeOpt>& opts) :b{ b }, opts{ opts }{}
	bool dfs(int targetId, int difNumLow, int difNumHigh)//推导出一个公式
	{
		if (m.id(b) != m.num() - 1)return false;
		if (ok(targetId, difNumLow, difNumHigh))return true;
		for (int i = 0; i < opts.size(); i++) {
			auto& opt = opts[i];
			opt.change();
			if (dfs(targetId, difNumLow, difNumHigh)) {
				cout << i << " ";
				return true;
			}
			opt.reback();
		}
		return false;
	}
private:
	bool ok(int targetId, int difNumLow, int difNumHigh)
	{
		for (int i = 0; i < b.size(); i++) {
			int c = b[i].changeNum();
			if (i != targetId) {
				if (c)return false;
			}
			else {
				if (c < difNumLow || c > difNumHigh)return false;
			}

		}
		return true;
	}
	vector<CubeBlock>&b;
	vector<CubeOpt>&opts;
	GetCombineId<CubeBlock>m;
};

先以金字塔二重奏魔方为例:

int main()
{
	CubeBlock block1(0, 4);//4角块
	CubeBlock block2(1, 4);//4棱块
	vector<CubeBlock>b = vector<CubeBlock>{ block1,block2 };
	vector<vector<int>>v1 = { {0,1,2,3},{1,2,0,3} };
	vector<vector<int>>v2 = { {0,1,2,3},{3,0,2,1} };
	vector<vector<int>>v3 = { {0,1,2,3},{0,3,1,2} };
	vector<vector<int>>v4 = { {0,1,2,3},{2,1,3,0} };
	CubeOpt op1(b, v1);
	CubeOpt op2(b, v2);
	CubeOpt op3(b, v3);
	CubeOpt op4(b, v4);
	vector<CubeOpt>opts = { op1,op2,op3,op4 };
	Cube(b, opts).dfs(1, 4, 4);
	return 0;
}

输出:

1 0 0(注意,输出的是倒着的,要按照001的顺序执行)

虽然并没有得到上面公式,但是这个结果是对的。

再用四轴旋转魔方试一下:

int main()
{
	CubeBlock block1(0, 8);//8角块
	CubeBlock block2(1, 6);//6棱块
	vector<CubeBlock>b = vector<CubeBlock>{ block1,block2 };
	vector<vector<int>>v1 = { {0,3,2,6,4,5,1,7},{2,1,5,3,4,0} };
	vector<vector<int>>v2 = { {2,1,5,3,4,0,6,7},{5,1,2,0,4,3} };
	vector<vector<int>>v3 = { {0,4,2,1,3,5,6,7},{3,1,2,4,0,5} };
	vector<vector<int>>v4 = { {7,1,0,3,4,5,6,2},{4,1,0,3,2,5} };
	CubeOpt op1(b, v1);
	CubeOpt op2(b, v2);
	CubeOpt op3(b, v3);
	CubeOpt op4(b, v4);
	vector<CubeOpt>opts = { op1,op2,op3,op4 };
	Cube(b, opts).dfs(1, 1, 6);
	return 0;
}

输出1 0 0 1 0 0 1 0 0

这相当于是一个6步的公式,把这个公式重复3遍就得到一个公式,它的效果是把一个四轴旋转魔方的前面和下面中心块交换,后面和右面中心块交换,其他块不变。

再拿233魔方试一下:

int main()
{
	CubeBlock block1(0, 8);//8角块
	CubeBlock block2(1, 8);//8棱块
	vector<CubeBlock>b = vector<CubeBlock>{ block1,block2 };
	vector<vector<int>>v1 = { {3,0,1,2,4,5,6,7},{3,0,1,2,4,5,6,7} };
	vector<vector<int>>v2 = { {0,6,5,3,4,2,1,7},{0,5,2,3,4,1,6,7} };
	vector<vector<int>>v3 = { {0,1,7,6,4,5,3,2},{0,1,6,3,4,5,2,7} };
	vector<vector<int>>v4 = { {7,1,2,4,3,5,6,0},{0,1,2,7,4,5,6,3} };
	vector<vector<int>>v5 = { {5,4,2,3,1,0,6,7},{4,1,2,3,0,5,6,7} };
	CubeOpt op1(b, v1);
	CubeOpt op2(b, v2);
	CubeOpt op3(b, v3);
	CubeOpt op4(b, v4);
	CubeOpt op5(b, v5);
	vector<CubeOpt>opts = { op1,op2,op3,op4,op5 };
	Cube(b, opts).dfs(1, 1, 4);
	return 0;
}

输出 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0

倒也算是一个公式,但是影响了5个棱块,不便于使用。

于是我们收紧搜索范围:Cube(b, opts).dfs(1, 1, 4);

输出

2 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 2 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 2 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0

这显然有点长,但是我们可以提取出

1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0

看看它的效果是啥,我们很快就发明了一个交换三个棱块的公式:

(正着的)0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1  0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1

然而,这还不够,我们需要的是指交换2个块的公式。

四,V1+开放式尝试

注意到上面对于233魔方的尝试,输出结果几乎全都是0和1,所以我们尝试只用0和1两种操作。

int main()
{
	CubeBlock block1(0, 8);//8角块
	CubeBlock block2(1, 8);//8棱块
	vector<CubeBlock>b = vector<CubeBlock>{ block1,block2 };
	vector<vector<int>>v1 = { {3,0,1,2,4,5,6,7},{3,0,1,2,4,5,6,7} };
	vector<vector<int>>v2 = { {0,6,5,3,4,2,1,7},{0,5,2,3,4,1,6,7} };
	vector<vector<int>>v3 = { {0,1,7,6,4,5,3,2},{0,1,6,3,4,5,2,7} };
	vector<vector<int>>v4 = { {7,1,2,4,3,5,6,0},{0,1,2,7,4,5,6,3} };
	vector<vector<int>>v5 = { {5,4,2,3,1,0,6,7},{4,1,2,3,0,5,6,7} };
	CubeOpt op1(b, v1);
	CubeOpt op2(b, v2);
	CubeOpt op3(b, v3);
	CubeOpt op4(b, v4);
	CubeOpt op5(b, v5);
	vector<CubeOpt>opts = {op1,op2};
	Cube(b, opts).dfs(1, 1, 2);
	return 0;
}

输出:

0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 1 0 0 0 1 0 0 0 1 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0  0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0

公式太长了,为了寻找最短路,我决定改成bfs

五,V2代码(BFS)

原本想着BFS需要GetSingleId类新增根据id获取数据的接口getData,于是把GetSingleId类改成:

template<typename T>
class GetSingleId
{
public:
	int id(T x)
	{
		auto it = m.find(Node{ &x });
		if (it != m.end())return it->second;
		v.push_back(x);
		m[Node{ v.data() + v.size() - 1 }] = n;
		return n++;
	}
	int num()
	{
		return n;
	}
	T* getData(int id)
	{
		return v.data() + id;
	}
private:
	struct Node {
		T* x;
		bool operator<(const Node& nod)const
		{
			return *x < *nod.x;
		}
	};
	vector<T>v;
	map<Node, int>m;
	int n = 0;
};

不知道为什么会报错(后来发现是v只要发生了resize就会出现错误)。

于是我先用一个低效版代替:

template<typename T>
class GetSingleId
{
public:
	int id(T x)
	{
		auto it = m.find(x);
		if (it != m.end())return it->second;
		return m[x] = n++;
	}
	int num()
	{
		return n;
	}
	T getData(int id)
	{
		for (auto& mi : m)if (mi.second == id)return mi.first;
		return T{};
	}
private:
	map<T, int>m;
	int n = 0;
};

template<typename T>
class GetCombineId
{
public:
	vector<int> combineId(vector<T>& x)
	{
		if (v.empty())v.resize(x.size());
		vector<int>ans(x.size());
		for (int i = 0; i < x.size(); i++)ans[i] = v[i].id(x[i]);
		return ans;
	}
	int id(vector<T>& x)
	{
		return v2.id(combineId(x));
	}
	int num()
	{
		return v2.num();
	}
	vector<T> getData(int id)
	{
		vector<int>ids = v2.getData(id);
		vector<T>ans(v.size());
		for (int i = 0; i < v.size(); i++)ans[i] = v[i].getData(ids[i]);
		return ans;
	}
private:
	vector<GetSingleId<T>>v;
	GetSingleId<vector<int>>v2;
};

struct CubeBlock
{
	int typeId;//角块,棱块等,分组id
	vector<int>v;//一组块
	CubeBlock() {}
	CubeBlock(int id, int n)
	{
		typeId = id;
		v.resize(n);
		for (int i = 0; i < n; i++)v[i] = i;//每一组的块都按从0开始编号
	}
	int changeNum()
	{
		int ans = 0;
		for (int i = 0; i < v.size(); i++)if (v[i] != i)ans++;
		return ans;
	}
	bool isOk()
	{
		return changeNum() == 0;
	}
	bool operator<(const CubeBlock& blocks)const
	{
		for (int i = 0; i < v.size() && i < blocks.v.size(); i++) {
			if (v[i] < blocks.v[i])return true;
			if (blocks.v[i] < v[i])return false;
		}
		return false;
	}
};

class CubeOpt
{
public:
	CubeOpt(vector<CubeBlock>& b, vector<vector<int>>& v) :b{ b }, v{ v }{}//若干组块及其变换
	CubeOpt& operator =(const CubeOpt& opt) {
		b = opt.b, v = opt.v;
		return *this;
	}
	void change()
	{
		for (int i = 0; i < v.size(); i++) {
			change(b[i], v[i]);
		}
	}
	void reback()
	{
		for (int i = 0; i < v.size(); i++) {
			reback(b[i], v[i]);
		}
	}
private:
	void change(CubeBlock& b, vector<int>& v)//一组块及一个变换,如v[1]=2表示把2号块移到1号块的位置
	{
		vector<int>bv = b.v;
		for (int i = 0; i < v.size(); i++) {
			b.v[i] = bv[v[i]];
		}
	}
	void reback(CubeBlock& b, vector<int>& v)
	{
		vector<int>bv = b.v;
		for (int i = 0; i < v.size(); i++) {
			b.v[v[i]] = bv[i];
		}
	}
	vector<vector<int>>& v;
	vector<CubeBlock>& b;
};

class Cube
{
public:
	Cube(vector<CubeBlock>& b, vector<CubeOpt>& opts) :b{ b }, opts{ opts }{}
	int bfs(int targetId, int difNumLow, int difNumHigh)//推导出一个公式
	{
		queue<vector<CubeBlock>>q;
		q.push(b);
		while (!q.empty()) {
			b = q.front();
			q.pop();
			if (ok(b, targetId, difNumLow, difNumHigh)) {
				return m.id(b);
			}
			int id = m.id(b);
			for (int i = 0; i < opts.size(); i++) {
				auto& opt = opts[i];
				opt.change();
				if (m.id(b) == m.num() - 1)q.push(b), fa[m.id(b)] = id;
				opt.reback();
			}
		}
		return 0;
	}
	vector<vector<CubeBlock>> getAns(int id)
	{
		vector<vector<CubeBlock>>v;
		while(id) {
			v.insert(v.begin(), m.getData(id));
			id = fa[id];
		}
		v.insert(v.begin(), m.getData(id));
		return v;
	}
private:
	bool ok(vector<CubeBlock>& b, int targetId, int difNumLow, int difNumHigh)
	{
		for (int i = 0; i < b.size(); i++) {
			int c = b[i].changeNum();
			if (i != targetId) {
				if (c)return false;
			}
			else {
				if (c < difNumLow || c > difNumHigh)return false;
			}

		}
		return true;
	}
	vector<CubeBlock>&b;
	vector<CubeOpt>&opts;
	GetCombineId<CubeBlock>m;
	map<int, int>fa;
};

还是以金字塔二重奏魔方为例:

int main()
{
	CubeBlock block1(0, 4);//4角块
	CubeBlock block2(1, 4);//4棱块
	vector<CubeBlock>b = vector<CubeBlock>{ block1,block2 };
	vector<vector<int>>v1 = { {0,1,2,3},{1,2,0,3} };
	vector<vector<int>>v2 = { {0,1,2,3},{3,0,2,1} };
	vector<vector<int>>v3 = { {0,1,2,3},{0,3,1,2} };
	vector<vector<int>>v4 = { {0,1,2,3},{2,1,3,0} };
	CubeOpt op1(b, v1);
	CubeOpt op2(b, v2);
	CubeOpt op3(b, v3);
	CubeOpt op4(b, v4);
	vector<CubeOpt>opts = { op1,op2,op3,op4 };
	Cube cube(b, opts);
	int ansId = cube.bfs(1, 4, 4);
	vector<vector<CubeBlock>> v = cube.getAns(ansId);
	for (int i = 1; i < v.size(); i++) {
		for (int j = 0; j < opts.size(); j++) {
			auto v1 = v[i - 1], v2 = v[i];
			b = v1;
			opts[j].change();
			bool same = true;
			for (int k = 0; k < v2.size(); k++)if (v2[k] < b[k] || b[k] < v2[k])same = false;
			if (same) {
				cout << j << " ";
				break;
			}
		}
	}
	return 0;
}

输出0 0 1(这个顺序是正着的

再用四轴旋转魔方试一下:

int main()
{
	CubeBlock block1(0, 8);//8角块
	CubeBlock block2(1, 6);//6棱块
	vector<CubeBlock>b = vector<CubeBlock>{ block1,block2 };
	vector<vector<int>>v1 = { {0,3,2,6,4,5,1,7},{2,1,5,3,4,0} };
	vector<vector<int>>v2 = { {2,1,5,3,4,0,6,7},{5,1,2,0,4,3} };
	vector<vector<int>>v3 = { {0,4,2,1,3,5,6,7},{3,1,2,4,0,5} };
	vector<vector<int>>v4 = { {7,1,0,3,4,5,6,2},{4,1,0,3,2,5} };
	CubeOpt op1(b, v1);
	CubeOpt op2(b, v2);
	CubeOpt op3(b, v3);
	CubeOpt op4(b, v4);
	vector<CubeOpt>opts = { op1,op2,op3,op4 };
	Cube cube(b, opts);
	int ansId = cube.bfs(1, 1, 6);
	vector<vector<CubeBlock>> v = cube.getAns(ansId);
	for (int i = 1; i < v.size(); i++) {
		for (int j = 0; j < opts.size(); j++) {
			auto v1 = v[i - 1], v2 = v[i];
			b = v1;
			opts[j].change();
			bool same = true;
			for (int k = 0; k < v2.size(); k++)if (v2[k] < b[k] || b[k] < v2[k])same = false;
			if (same) {
				cout << j << " ";
				break;
			}
			if (j == opts.size() - 1)cout << "? ";
		}
	}
	return 0;
}

输出:0 0 1 0 1 1

再拿233魔方试一下:

int main()
{
	CubeBlock block1(0, 8);//8角块
	CubeBlock block2(1, 8);//8棱块
	vector<CubeBlock>b = vector<CubeBlock>{ block1,block2 };
	vector<vector<int>>v1 = { {3,0,1,2,4,5,6,7},{3,0,1,2,4,5,6,7} };
	vector<vector<int>>v2 = { {0,6,5,3,4,2,1,7},{0,5,2,3,4,1,6,7} };
	vector<vector<int>>v3 = { {0,1,7,6,4,5,3,2},{0,1,6,3,4,5,2,7} };
	vector<vector<int>>v4 = { {7,1,2,4,3,5,6,0},{0,1,2,7,4,5,6,3} };
	vector<vector<int>>v5 = { {5,4,2,3,1,0,6,7},{4,1,2,3,0,5,6,7} };
	CubeOpt op1(b, v1);
	CubeOpt op2(b, v2);
	CubeOpt op3(b, v3);
	CubeOpt op4(b, v4);
	CubeOpt op5(b, v5);
	vector<CubeOpt>opts = { op1,op2,op3,op4,op5 };
	Cube cube(b, opts);
	int ansId = cube.bfs(1, 1, 3);
	vector<vector<CubeBlock>> v = cube.getAns(ansId);
	for (int i = 1; i < v.size(); i++) {
		for (int j = 0; j < opts.size(); j++) {
			auto v1 = v[i - 1], v2 = v[i];
			b = v1;
			opts[j].change();
			bool same = true;
			for (int k = 0; k < v2.size(); k++)if (v2[k] < b[k] || b[k] < v2[k])same = false;
			if (same) {
				cout << j << " ";
				break;
			}
			if (j == opts.size() - 1)cout << "? ";
		}
	}
	return 0;
}

输出:0 0 1 0 0 1 0 0 1

这个公式的效果是交换顶层不相邻的2个棱块。

六,V3代码(BFS)

为了方便,我把代码改成可以输出多个解的形式,而不是只找到一个解就结束。

template<typename T>
class GetSingleId
{
public:
	int id(T x)
	{
		auto it = m.find(x);
		if (it != m.end())return it->second;
		return m[x] = n++;
	}
	int num()
	{
		return n;
	}
	T getData(int id)
	{
		for (auto& mi : m)if (mi.second == id)return mi.first;
		return T{};
	}
private:
	map<T, int>m;
	int n = 0;
};

template<typename T>
class GetCombineId
{
public:
	vector<int> combineId(vector<T>& x)
	{
		if (v.empty())v.resize(x.size());
		vector<int>ans(x.size());
		for (int i = 0; i < x.size(); i++)ans[i] = v[i].id(x[i]);
		return ans;
	}
	int id(vector<T>& x)
	{
		return v2.id(combineId(x));
	}
	int num()
	{
		return v2.num();
	}
	vector<T> getData(int id)
	{
		vector<int>ids = v2.getData(id);
		vector<T>ans(v.size());
		for (int i = 0; i < v.size(); i++)ans[i] = v[i].getData(ids[i]);
		return ans;
	}
private:
	vector<GetSingleId<T>>v;
	GetSingleId<vector<int>>v2;
};

struct CubeBlock
{
	int typeId;//角块,棱块等,分组id
	vector<int>v;//一组块
	CubeBlock() {}
	CubeBlock(int id, int n)
	{
		typeId = id;
		v.resize(n);
		for (int i = 0; i < n; i++)v[i] = i;//每一组的块都按从0开始编号
	}
	int changeNum()
	{
		int ans = 0;
		for (int i = 0; i < v.size(); i++)if (v[i] != i)ans++;
		return ans;
	}
	bool isOk()
	{
		return changeNum() == 0;
	}
	bool operator<(const CubeBlock& blocks)const
	{
		for (int i = 0; i < v.size() && i < blocks.v.size(); i++) {
			if (v[i] < blocks.v[i])return true;
			if (blocks.v[i] < v[i])return false;
		}
		return false;
	}
};

class CubeOpt
{
public:
	CubeOpt(vector<CubeBlock>& b, vector<vector<int>>& v) :b{ b }, v{ v }{}//若干组块及其变换
	CubeOpt& operator =(const CubeOpt& opt) {
		b = opt.b, v = opt.v;
		return *this;
	}
	void change()
	{
		for (int i = 0; i < v.size(); i++) {
			change(b[i], v[i]);
		}
	}
	void reback()
	{
		for (int i = 0; i < v.size(); i++) {
			reback(b[i], v[i]);
		}
	}
private:
	void change(CubeBlock& b, vector<int>& v)//一组块及一个变换,如v[1]=2表示把2号块移到1号块的位置
	{
		vector<int>bv = b.v;
		for (int i = 0; i < v.size(); i++) {
			b.v[i] = bv[v[i]];
		}
	}
	void reback(CubeBlock& b, vector<int>& v)
	{
		vector<int>bv = b.v;
		for (int i = 0; i < v.size(); i++) {
			b.v[v[i]] = bv[i];
		}
	}
	vector<vector<int>>& v;
	vector<CubeBlock>& b;
};

class Cube
{
public:
	Cube(vector<CubeBlock>& b, vector<CubeOpt>& opts) :b{ b }, opts{ opts }{}
	int bfs(int targetId, int difNumLow, int difNumHigh)//推导出一个公式
	{
		queue<vector<CubeBlock>>q;
		q.push(b);
		while (!q.empty()) {
			b = q.front();
			q.pop();
			if (ok(b, targetId, difNumLow, difNumHigh)) {
				showAns(m.id(b));
			}
			int id = m.id(b);
			for (int i = 0; i < opts.size(); i++) {
				auto& opt = opts[i];
				opt.change();
				if (m.id(b) == m.num() - 1)q.push(b), fa[m.id(b)] = id;
				opt.reback();
			}
		}
		return 0;
	}
	vector<vector<CubeBlock>> getAns(int id)
	{
		vector<vector<CubeBlock>>v;
		while (id) {
			v.insert(v.begin(), m.getData(id));
			id = fa[id];
		}
		v.insert(v.begin(), m.getData(id));
		return v;
	}
	void showAns(int ansId)
	{
		vector<vector<CubeBlock>> v = getAns(ansId);
		for (int i = 1; i < v.size(); i++) {
			for (int j = 0; j < opts.size(); j++) {
				auto v1 = v[i - 1], v2 = v[i];
				b = v1;
				opts[j].change();
				bool same = true;
				for (int k = 0; k < v2.size(); k++)if (v2[k] < b[k] || b[k] < v2[k])same = false;
				if (same) {
					cout << j << " ";
					break;
				}
				if (j == opts.size() - 1)cout << "? ";
			}
		}
		cout << endl;
	}
private:
	bool ok(vector<CubeBlock>& b, int targetId, int difNumLow, int difNumHigh)
	{
		for (int i = 0; i < b.size(); i++) {
			int c = b[i].changeNum();
			if (i != targetId) {
				if (c)return false;
			}
			else {
				if (c < difNumLow || c > difNumHigh)return false;
			}

		}
		return true;
	}
	vector<CubeBlock>& b;
	vector<CubeOpt>& opts;
	GetCombineId<CubeBlock>m;
	map<int, int>fa;
};

拿233魔方试一下:

int main()
{
	CubeBlock block1(0, 8);//8角块
	CubeBlock block2(1, 8);//8棱块
	vector<CubeBlock>b = vector<CubeBlock>{ block1,block2 };
	vector<vector<int>>v1 = { {3,0,1,2,4,5,6,7},{3,0,1,2,4,5,6,7} };
	vector<vector<int>>v2 = { {0,6,5,3,4,2,1,7},{0,5,2,3,4,1,6,7} };
	vector<vector<int>>v3 = { {0,1,7,6,4,5,3,2},{0,1,6,3,4,5,2,7} };
	vector<vector<int>>v4 = { {7,1,2,4,3,5,6,0},{0,1,2,7,4,5,6,3} };
	vector<vector<int>>v5 = { {5,4,2,3,1,0,6,7},{4,1,2,3,0,5,6,7} };
	CubeOpt op1(b, v1);
	CubeOpt op2(b, v2);
	CubeOpt op3(b, v3);
	CubeOpt op4(b, v4);
	CubeOpt op5(b, v5);
	vector<CubeOpt>opts = { op1,op2,op3,op4,op5 };
	Cube cube(b, opts);
	cube.bfs(1, 2, 2);
	return 0;
}

输出:

0 0 1 0 0 1 0 0 1
0 0 2 0 0 2 0 0 2
0 0 1 0 0 2 1 0 0 1 2
0 0 1 0 0 4 1 0 0 1 4
0 0 1 2 0 0 2 1 0 0 2
0 0 2 0 0 1 2 0 0 2 1
0 0 2 0 0 3 2 0 0 2 3
0 0 2 1 0 0 1 2 0 0 1
0 0 3 2 0 0 2 3 0 0 2
0 0 4 1 0 0 1 4 0 0 1
0 1 0 0 2 1 0 0 1 2 0
0 1 0 0 4 1 0 0 1 4 0
0 1 2 0 0 2 1 0 0 2 0
0 2 0 0 1 2 0 0 2 1 0
0 2 0 0 3 2 0 0 2 3 0
0 2 1 0 0 1 2 0 0 1 0
0 3 2 0 0 2 3 0 0 2 0
0 4 1 0 0 1 4 0 0 1 0

七,V3+开放性尝试

看V3的结果,1234四个操作里面都只涉及2个操作,于是我们收缩搜索范围。

int main()
{
	CubeBlock block1(0, 8);//8角块
	CubeBlock block2(1, 8);//8棱块
	vector<CubeBlock>b = vector<CubeBlock>{ block1,block2 };
	vector<vector<int>>v1 = { {3,0,1,2,4,5,6,7},{3,0,1,2,4,5,6,7} };
	vector<vector<int>>v2 = { {0,6,5,3,4,2,1,7},{0,5,2,3,4,1,6,7} };
	vector<vector<int>>v3 = { {0,1,7,6,4,5,3,2},{0,1,6,3,4,5,2,7} };
	vector<vector<int>>v4 = { {7,1,2,4,3,5,6,0},{0,1,2,7,4,5,6,3} };
	vector<vector<int>>v5 = { {5,4,2,3,1,0,6,7},{4,1,2,3,0,5,6,7} };
	CubeOpt op1(b, v1);
	CubeOpt op2(b, v2);
	CubeOpt op3(b, v3);
	CubeOpt op4(b, v4);
	CubeOpt op5(b, v5);
	vector<CubeOpt>opts = { op1,op2,op3};
	Cube cube(b, opts);
	cube.bfs(1, 2, 2);
	return 0;
}

在输出的若干解中,选出这一行:

1 0 1 2 0 0 2 1 0 0 2 0 1

这就是交换相邻2个棱块的公式。

如果在ok函数中新增一行

for (int j = 4; j < 8; j++)if (b[i].v[j] != j)return false;

则部分输出被过滤掉,更加方便。

八,V4代码(可视化优化)

基于V3的代码做了微调,使得看起来更容易

template<typename T>
class GetSingleId
{
public:
	int id(T x)
	{
		auto it = m.find(x);
		if (it != m.end())return it->second;
		return m[x] = n++;
	}
	int num()
	{
		return n;
	}
	T getData(int id)
	{
		for (auto& mi : m)if (mi.second == id)return mi.first;
		return T{};
	}
private:
	map<T, int>m;
	int n = 0;
};

template<typename T>
class GetCombineId
{
public:
	vector<int> combineId(vector<T>& x)
	{
		if (v.empty())v.resize(x.size());
		vector<int>ans(x.size());
		for (int i = 0; i < x.size(); i++)ans[i] = v[i].id(x[i]);
		return ans;
	}
	int id(vector<T>& x)
	{
		return v2.id(combineId(x));
	}
	int num()
	{
		return v2.num();
	}
	vector<T> getData(int id)
	{
		vector<int>ids = v2.getData(id);
		vector<T>ans(v.size());
		for (int i = 0; i < v.size(); i++)ans[i] = v[i].getData(ids[i]);
		return ans;
	}
private:
	vector<GetSingleId<T>>v;
	GetSingleId<vector<int>>v2;
};

struct CubeBlock
{
	int typeId;//角块,棱块等,分组id
	vector<int>v;//一组块
	CubeBlock() {}
	CubeBlock(int id, int n)
	{
		typeId = id;
		v.resize(n);
		for (int i = 0; i < n; i++)v[i] = i;//每一组的块都按从0开始编号
	}
	int changeNum()
	{
		int ans = 0;
		for (int i = 0; i < v.size(); i++)if (v[i] != i)ans++;
		return ans;
	}
	bool isOk()
	{
		return changeNum() == 0;
	}
	bool operator<(const CubeBlock& blocks)const
	{
		for (int i = 0; i < v.size() && i < blocks.v.size(); i++) {
			if (v[i] < blocks.v[i])return true;
			if (blocks.v[i] < v[i])return false;
		}
		return false;
	}
};

class CubeOpt
{
public:
	CubeOpt(vector<CubeBlock>& b, vector<vector<int>>& v) :b{ b }, v{ v }{}//若干组块及其变换
	CubeOpt& operator =(const CubeOpt& opt) {
		b = opt.b, v = opt.v;
		return *this;
	}
	void change()
	{
		for (int i = 0; i < v.size(); i++) {
			change(b[i], v[i]);
		}
	}
	void reback()
	{
		for (int i = 0; i < v.size(); i++) {
			reback(b[i], v[i]);
		}
	}
private:
	void change(CubeBlock& b, vector<int>& v)//一组块及一个变换,如v[1]=2表示把2号块移到1号块的位置
	{
		vector<int>bv = b.v;
		for (int i = 0; i < v.size(); i++) {
			b.v[i] = bv[v[i]];
		}
	}
	void reback(CubeBlock& b, vector<int>& v)
	{
		vector<int>bv = b.v;
		for (int i = 0; i < v.size(); i++) {
			b.v[v[i]] = bv[i];
		}
	}
	vector<vector<int>>& v;
	vector<CubeBlock>& b;
};

map<int, string>mans;
class Cube
{
public:
	Cube(vector<CubeBlock>& b, vector<CubeOpt>& opts) :b{ b }, opts{ opts }{}
	int bfs(int targetId, int difNumLow, int difNumHigh)//推导出一个公式
	{
		queue<vector<CubeBlock>>q;
		q.push(b);
		while (!q.empty()) {
			b = q.front();
			q.pop();
			if (ok(b, targetId, difNumLow, difNumHigh)) {
				for (int i = 0; i < b[targetId].v.size(); i++)cout << b[targetId].v[i] << " ";
				cout << "     ";
				showAns(m.id(b));
			}
			int id = m.id(b);
			for (int i = 0; i < opts.size(); i++) {
				auto& opt = opts[i];
				opt.change();
				if (m.id(b) == m.num() - 1)q.push(b), fa[m.id(b)] = id;
				opt.reback();
			}
		}
		return 0;
	}
	vector<vector<CubeBlock>> getAns(int id)
	{
		vector<vector<CubeBlock>>v;
		while (id) {
			v.insert(v.begin(), m.getData(id));
			id = fa[id];
		}
		v.insert(v.begin(), m.getData(id));
		return v;
	}
	void showAns(int ansId)
	{
		vector<vector<CubeBlock>> v = getAns(ansId);
		for (int i = 1; i < v.size(); i++) {
			for (int j = 0; j < opts.size(); j++) {
				auto v1 = v[i - 1], v2 = v[i];
				b = v1;
				opts[j].change();
				bool same = true;
				for (int k = 0; k < v2.size(); k++)if (v2[k] < b[k] || b[k] < v2[k])same = false;
				if (same) {
					cout <<j<< mans[j] << " ";
					break;
				}
				if (j == opts.size() - 1)cout << "? ";
			}
		}
		cout << endl;
	}
private:
	bool ok(vector<CubeBlock>& b, int targetId, int difNumLow, int difNumHigh)
	{
		for (int i = 0; i < b.size(); i++) {
			int c = b[i].changeNum();
			if (i != targetId) {
				if (c)return false;
			}
			else {
				if (c < difNumLow || c > difNumHigh)return false;
			}
		}
		return true;
	}
	vector<CubeBlock>& b;
	vector<CubeOpt>& opts;
	GetCombineId<CubeBlock>m;
	map<int, int>fa;
};

以五阶齿轮魔方为例:

int main()
{
	CubeBlock block1(0, 8);//8角块
	CubeBlock block2(1, 12);//12棱块
	vector<CubeBlock>b = vector<CubeBlock>{ block1,block2 };
	mans[0] = "上", mans[1] = "下", mans[2] = "前", mans[3] = "后", mans[4] = "左", mans[5] = "右";
	vector<vector<int>>v1 = { {2,3,0,1,4,5,6,7},{2,3,0,1,4,5,6,7,8,9,10,11} };
	vector<vector<int>>v2 = { {0,1,2,3,6,7,4,5},{0,1,2,3,4,5,6,7,10,11,8,9} };
	vector<vector<int>>v3 = { {0,1,7,6,4,5,3,2},{0,1,10,3,4,5,7,6,8,9,2,11} };
	vector<vector<int>>v4 = { {5,4,2,3,1,0,6,7},{8,1,2,3,5,4,6,7,0,9,10,11} };
	vector<vector<int>>v5 = { {7,1,2,4,3,5,6,0},{0,1,2,11,7,5,6,4,8,9,10,3} };
	vector<vector<int>>v6 = { {0,6,5,3,4,2,1,7},{0,9,2,3,4,6,5,7,8,1,10,11} };
	CubeOpt op1(b, v1);
	CubeOpt op2(b, v2);
	CubeOpt op3(b, v3);
	CubeOpt op4(b, v4);
	CubeOpt op5(b, v5);
	CubeOpt op6(b, v6);
	vector<CubeOpt>opts = { op1,op2,op3,op4,op5,op6 };
	Cube cube(b, opts);
	cube.bfs(1, 2, 4);
	return 0;
}

在输出的若干行中,很容易就能选出这3行:

0 1 2 3 5 4 7 6 8 9 10 11      0上 1下 2前 0上 1下 3后
0 3 2 1 4 5 7 6 8 9 10 11      0上 2前 0上 2前 0上 2前
0 1 2 3 7 5 4 6 8 9 10 11      0上 2前 0上 4左 0上 2前 0上 4左

九,搜索加速

为了加快搜索效率,我们可以用一个系列操作当成一个原子操作,加入到搜索列表中。

需要注意的是轮换对称性的情况,要区分“轮换对称性意义下的不改变另外一组块”和“不改变另外一组所有块的位置”。

当我们有了一个系列操作,我们可能还是需要知道它对另外一组块的具体作用,用一个小代码跑一下就行:

void test(vector<vector<int>>v, vector<int>id)
{
	vector<int>x = v[0];
	for (int i = 0; i < x.size(); i++)x[i] = i;
	for (auto k : id) {
		auto& vi = v[k];
		auto tmp = x;
		for (int i = 0; i < x.size(); i++)x[i] = tmp[vi[i]];
	}
	for (int i = 0; i < x.size(); i++)cout << x[i] << ",";
}

参考五阶齿轮魔方

十,V5代码(便于开放性尝试和加速搜索)

为了方便开放性尝试和加速搜索,把代码做了微调


template<typename T>
class GetSingleId
{
public:
	int id(T x)
	{
		auto it = m.find(x);
		if (it != m.end())return it->second;
		return m[x] = n++;
	}
	int num()
	{
		return n;
	}
	T getData(int id)
	{
		for (auto& mi : m)if (mi.second == id)return mi.first;
		return T{};
	}
private:
	map<T, int>m;
	int n = 0;
};

template<typename T>
class GetCombineId
{
public:
	vector<int> combineId(vector<T>& x)
	{
		if (v.empty())v.resize(x.size());
		vector<int>ans(x.size());
		for (int i = 0; i < x.size(); i++)ans[i] = v[i].id(x[i]);
		return ans;
	}
	int id(vector<T>& x)
	{
		return v2.id(combineId(x));
	}
	int num()
	{
		return v2.num();
	}
	vector<T> getData(int id)
	{
		vector<int>ids = v2.getData(id);
		vector<T>ans(v.size());
		for (int i = 0; i < v.size(); i++)ans[i] = v[i].getData(ids[i]);
		return ans;
	}
private:
	vector<GetSingleId<T>>v;
	GetSingleId<vector<int>>v2;
};

struct CubeBlock
{
	int typeId;//角块,棱块等,分组id
	vector<int>v;//一组块
	CubeBlock() {}
	CubeBlock(int id, int n)
	{
		typeId = id;
		v.resize(n);
		for (int i = 0; i < n; i++)v[i] = i;//每一组的块都按从0开始编号
	}
	int changeNum()
	{
		int ans = 0;
		for (int i = 0; i < v.size(); i++) {
			if (v[i]/4 != i/4)ans++;
		}
		return ans;
	}
	bool isOk()
	{
		return changeNum() == 0;
	}
	bool operator<(const CubeBlock& blocks)const
	{
		for (int i = 0; i < v.size() && i < blocks.v.size(); i++) {
			if (v[i] < blocks.v[i])return true;
			if (blocks.v[i] < v[i])return false;
		}
		return false;
	}
};

struct Opt {
	vector<vector<int>>v;
	string s;
};

class CubeOpt
{
public:
	CubeOpt(vector<CubeBlock>& b, Opt& opt) :b{ b }, v{ opt.v }, s{ opt.s }{}//若干组块及其变换
	CubeOpt& operator =(const CubeOpt& opt) {
		b = opt.b, v = opt.v;
		return *this;
	}
	void change()
	{
		for (int i = 0; i < v.size(); i++) {
			change(b[i], v[i]);
		}
	}
	void reback()
	{
		for (int i = 0; i < v.size(); i++) {
			reback(b[i], v[i]);
		}
	}
	string getName()
	{
		return s;
	}
private:
	void change(CubeBlock& b, vector<int>& v)//一组块及一个变换,如v[1]=2表示把2号块移到1号块的位置
	{
		vector<int>bv = b.v;
		for (int i = 0; i < v.size(); i++) {
			b.v[i] = bv[v[i]];
		}
	}
	void reback(CubeBlock& b, vector<int>& v)
	{
		vector<int>bv = b.v;
		for (int i = 0; i < v.size(); i++) {
			b.v[v[i]] = bv[i];
		}
	}
	vector<vector<int>>& v;
	vector<CubeBlock>& b;
	string s;
};

map<int, string>mans;
class Cube
{
public:
	Cube(vector<CubeBlock>& b, vector<CubeOpt>& opts) :b{ b }, opts{ opts }{}
	int bfs(int targetId, int difNumLow, int difNumHigh)//推导出一个公式
	{
		queue<vector<CubeBlock>>q;
		q.push(b);
		int k = 0;
		while (!q.empty()) {
			 k++;
			// if (k % 10000 == 0)cout << k << " "<<q.size()<<"  ";
			b = q.front();
			q.pop();
			if (ok(b, targetId, difNumLow, difNumHigh)) {
				for (auto& bi : b) {
					for (int i = 0; i < bi.v.size(); i++)cout << bi.v[i] << ",";
					cout << "\n";
				}
				showAns(m.id(b));
				cout << "\n";
			}
			int id = m.id(b);
			for (int i = 0; i < opts.size(); i++) {
				auto& opt = opts[i];
				opt.change();
				if (m.id(b) == m.num() - 1)if(q.size()<100000)q.push(b), fa[m.id(b)] = id;
				opt.reback();
				//if (q.size() % 10000 == 0)cout << q.size()<<" ";
			}
		}
		return 0;
	}
	vector<vector<CubeBlock>> getAns(int id)
	{
		vector<vector<CubeBlock>>v;
		while (id) {
			v.insert(v.begin(), m.getData(id));
			id = fa[id];
		}
		v.insert(v.begin(), m.getData(id));
		return v;
	}
	void showAns(int ansId)
	{
		vector<vector<CubeBlock>> v = getAns(ansId);
		for (int i = 1; i < v.size(); i++) {
			for (int j = 0; j < opts.size(); j++) {
				auto v1 = v[i - 1], v2 = v[i];
				b = v1;
				opts[j].change();
				bool same = true;
				for (int k = 0; k < v2.size(); k++)if (v2[k] < b[k] || b[k] < v2[k])same = false;
				if (same) {
					cout <<j<< mans[j] << " ";
					break;
				}
				if (j == opts.size() - 1)cout << "? ";
			}
		}
		cout << endl;
	}
private:
	bool ok(vector<CubeBlock>& b, int targetId, int difNumLow, int difNumHigh)
	{
		for (int i = 0; i < b.size(); i++) {
			int c = b[i].changeNum();
			if (i != targetId) {
				if (c)return false;
			}
			else {
				if (c < difNumLow || c > difNumHigh)return false;
			}
		}
		return true;
	}
	vector<CubeBlock>& b;
	vector<CubeOpt>& opts;
	GetCombineId<CubeBlock>m;
	map<int, int>fa;
};

void test(vector<vector<int>>v, vector<int>id)
{
	vector<int>x = v[0];
	for (int i = 0; i < x.size(); i++)x[i] = i;
	for (auto k : id) {
		auto& vi = v[k];
		auto tmp = x;
		for (int i = 0; i < x.size(); i++)x[i] = tmp[vi[i]];
	}
	for (int i = 0; i < x.size(); i++)cout << x[i] << ",";
}

main函数示例:

int main()
{
	CubeBlock block1(0, 24);//24侧边区侧棱
	CubeBlock block2(1, 24);//24中心区棱块
	vector<CubeBlock>b = vector<CubeBlock>{ block1,block2 };
	Opt opt1{ { {4,5,6,7,0,1,2,3,11,8,9,10,12,13,14,15,16,17,18,19,20,21,22,23},
	{2,3,0,1,4,5,6,7,20,9,10,11,16,13,14,15,8,17,18,19,12,21,22,23} } ,"上" };
	Opt opt2{ { {0,1,2,3,4,5,6,7,8,9,10,11,13,14,15,12,20,21,22,23,16,17,18,19},
	{0,1,2,3,6,7,4,5,8,9,18,11,12,13,22,15,16,17,14,19,20,21,10,23} } ,"下" };
	Opt opt3{ { {0,1,2,6,21,20,22,7,8,14,13,11,12,10,9,15,16,17,18,3,5,4,19,23} ,
	{0,1,17,3,4,5,23,7,10,11,8,9,12,13,14,15,16,6,18,19,20,21,22,2} } ,"前" };
	Opt opt4{ { {17,16,18,3,4,5,6,2,15,9,10,12,11,13,14,8,1,0,23,19,20,21,22,7},
	{21,1,2,3,19,5,6,7,8,9,10,11,14,15,12,13,16,17,18,0,20,4,22,23} } ,"后" };
	Opt opt5{ { {16,1,2,3,4,0,23,22,8,9,15,14,12,13,11,10,21,17,18,19,20,5,7,6},
	{0,1,2,13,4,5,6,11,8,9,10,3,12,7,14,15,18,19,16,17,20,21,22,23} } ,"左" };
	Opt opt6{ { {0,4,19,18,20,5,6,7,13,12,10,11,9,8,14,15,16,1,3,2,17,21,22,23},
	{0,9,2,3,4,15,6,7,8,5,10,11,12,13,14,1,16,17,18,19,22,23,20,21} } ,"右" };
	CubeOpt op1(b, opt1);
	CubeOpt op2(b, opt2);
	CubeOpt op3(b, opt3);
	CubeOpt op4(b, opt4);
	CubeOpt op5(b, opt5);
	CubeOpt op6(b, opt6);
	vector<CubeOpt>opts = { op1,op2,op3,op5 };
	for (int i = 0; i < opts.size(); i++)mans[i] = opts[i].getName();
	Cube cube(b, opts);
	cube.bfs(0, 1, 8);
	return 0;
}

这样在做开放式尝试时,就只需要修改操作列表opts,不需要修改mans了。

十一,全文总结

1,三阶魔方默认编号

8个角块,上面0-3,下面4-7

12个棱块,上面0-3,中间4-7,下面8-11

2,代码使用总结

只使用最新版的代码即可,除了main函数之外,其他代码对所有魔方都是一样的。

但是,如果想对搜索的公式做进一步限制,可以在ok函数里面加特定条件,这个就是每个魔方独有的代码。(这个其实也可以抽象出来,把差异都放到main函数,懒得弄了)

如果想做开放性尝试,即vector<CubeOpt>opts不取全集,只取一部分,需要修改mans

如果想增加关于块的对称性的信息(如4个一样的块),可以定制化修改changeNum函数。

3,分组总结

基础分组是按照位置可达进行分组。

三阶魔方的层先法,是基于基础分组的更细的分组。

块的对称性也是基于基础分组,如24个块其实可以分成6组,每组4个块都不区分。

4,搜索加速

搜索加速的技巧,参考第9章。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值