爱丽丝的幻方 题解
题意
如下图。
Statement


思路
请先了解 刚体变换 。
后文一切下标从 \(0\) 开始计算。
读题后不难发现,所有六种操作都是对一个矩阵进行刚体变换。换句话说,在变换过程中,矩阵各元素的相对位置保持不变。一个特例是平移操作,但其本质依然如此,只是需要加上取模。
刻画局面
既然是刚体变换,我们可以考虑通过一个参照点和两个方向向量来刻画一个局面。我们不妨选定数字 \(1\) 为参照。因为幻方合法,所以目标状态中,\(2\) 一定在 \(1\) 右侧,\(n+1\) 一定在 \(1\) 下方。所以我们知道 \(1\to 2\) 的向量 \(\vec{A}\) 和 \(1\to n+1\) 的向量 \(\vec{B}\) 一定垂直。考虑局面总数:\(1\) 所在位置可能有 \(n^2\) 个,而向量 \(\vec{A}\) 和 \(\vec{B}\) 的位置关系有 \(8\) 种(见下图),故最多有 \(8n^2\) 个局面。使用 bfs 求最小距离可以接受。
于是我们这样定义局面:\((id, r, c)\) 表示当前局面中,\(1\) 处在 \((r, c)\) 位置,且向量 \(\vec{A}\) 和 \(\vec{B}\) 组成第 \(id\) 种位置关系。这里 \(8\) 种位置关系可以自由编号。一种编号方式如下:
1 - 2 2 - 1 n+1 n+1 2 2 1 - n+1 n+1 - 1
| | | | | | | |
n+1 n+1 1 - 2 2 - 1 1 - n+1 n+1 - 1 2 2
(0) (1) (2) (3) (4) (5) (6) (7)
局面的转移
那么接下来就该考虑如何实现这六种操作。
首先考虑 \(id\) 的转移。这里没什么技巧,因为编号是人为的。我们只需要手动模拟共 \(48\) 种转移,打表即可。
然后考虑 \((r, c)\) 的转移。根据刚体变换的定义,我们有
其中 \(\vec{p}\) 和 \(\vec{p'}\) 是变换前后的坐标,看作列向量;\(R\) 是正交矩阵,\(\vec{t}\) 是平移向量。我们只需要对每种操作构造对应的 \(R\) 和 \(\vec{t}\) 即可。
以顺旋操作为例,我们要找 \(R\) 和 \(\vec{t}\) 满足
并且我们知道 \(r'=c\),\(c'=n-r-1\),所以我们不难构造出
以此类推,我们可以构造出其他所有操作的 \(R\) 和 \(\vec{t}\) 。列举如下:
-
右移
\[R=\begin{bmatrix} 1 &0 \\ 0 &1 \end{bmatrix} ,\ \vec{t}=\begin{bmatrix} 0 \\ 1 \end{bmatrix} \] -
下移
\[R=\begin{bmatrix} 1 &0 \\ 0 &1 \end{bmatrix} ,\ \vec{t}=\begin{bmatrix} 1 \\ 0 \end{bmatrix} \] -
转置
\[R=\begin{bmatrix} 0 &1 \\ 1 &0 \end{bmatrix} ,\ \vec{t}=\begin{bmatrix} 0 \\ 0 \end{bmatrix} \] -
纵向镜像
\[R=\begin{bmatrix} 1 &0 \\ 0 &-1 \end{bmatrix} ,\ \vec{t}=\begin{bmatrix} 0 \\ n-1 \end{bmatrix} \] -
横向镜像
\[R=\begin{bmatrix} -1 &0 \\ 0 &1 \end{bmatrix} ,\ \vec{t}=\begin{bmatrix} n-1 \\ 0 \end{bmatrix} \]
转移时,套用上面的公式即可求出 \((r',c')\)。需要注意的是,\(r'\) 和 \(c'\) 必须时刻对 \(n\) 取模。
去重的处理
这之后还有一个问题需要解决,就是 bfs 如何去重。我们考虑哈希。因为需要处理的参数只有三个,我们使用如下的哈希函数:
这样最大的哈希值不会超过 \(9001000\),可以用桶数组去重。一个技巧是 bfs 时直接把哈希值存入队列,处理队头时根据上面的公式反推出 \((id, r, c)\)。
边界条件
最后是起终点的处理。根据上面的描述,终点是 \((0,0,0)\),哈希值为 \(0\) 。考虑起点。事实上,我们不用记录整个初始矩阵。我们只关心 \(1\),\(2\),\(n+1\) 这三个数的位置。找到他们的位置后,即可算出方向向量 \(\vec{A}\) 和 \(\vec{B}\),继而匹配到对应的 \(8\) 个 \(id\) 之一即可。
复杂度
时间复杂度 \(O(8n^2)\) 。由于哈希函数的设计,空间占用略大,不超过 \(O(9n^2)\) 。
代码
#include<iostream>
#include<queue>
#include<cstring>
#define fastio ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define plus(x) ((x % n + n) % n)
#define MIKU 0
using namespace std;
int T, n;
struct State { int id, r, c; };
int R[6][4] = { //预处理正交矩阵
{ 1, 0, 0, 1},
{ 1, 0, 0, 1},
{ 0, 1, 1, 0},
{ 0, 1, -1, 0},
{ 1, 0, 0, -1},
{-1, 0, 0, 1}
};
int trans[6][8] = { //预处理 id 的转移
{0, 1, 2, 3, 4, 5, 6, 7},
{0, 1, 2, 3, 4, 5, 6, 7},
{6, 4, 7, 5, 1, 3, 0, 2},
{7, 5, 6, 4, 0, 2, 1, 3},
{1, 0, 3, 2, 5, 4, 7, 6},
{2, 3, 0, 1, 6, 7, 4, 5}
};
int ids[8][4] = { //预处理 id 的匹配
{ 0, 1, 1, 0},
{ 0, -1, 1, 0},
{ 0, 1, -1, 0},
{ 0, -1, -1, 0},
{-1, 0, 0, 1},
{-1, 0, 0, -1},
{ 1, 0, 0, 1},
{ 1, 0, 0, -1}
};
int t[6][2], dis[9001005];
void init() {
int tmp[6][2] = { //根据 n 处理平移向量
{0, 1},
{1, 0},
{0, 0},
{0, n-1},
{0, n-1},
{n-1, 0}
};
memcpy(t, tmp, sizeof(tmp));
memset(dis, -1, sizeof(dis));
}
int Hash(State s) { //计算哈希值
return s.id * n * n + s.r * n + s.c;
}
int bfs(State s) {
int res = 0;
queue<int> q; //哈希值入队
q.push(Hash(s)); dis[Hash(s)] = 0;
while(!q.empty()) {
int h = q.front(), d = dis[h]; q.pop();
if(h == 0) { res = dis[h]; break; } //到达终点
int c = h % n; h /= n; //反推三个参数
int r = h % n; h /= n;
int id = h;
for(int i=0; i<6; i++) {
int nid = trans[i][id];
int nr = plus(R[i][0] * r + R[i][1] * c + t[i][0]); //只是矩阵乘法
int nc = plus(R[i][2] * r + R[i][3] * c + t[i][1]); //记得取模
int H = Hash({nid, nr, nc});
if(dis[H] == -1) { //去重
q.push(H); dis[H] = d + 1;
}
}
}
return res;
}
int cg(int d) { //处理因平移导致的错位
if(d < -1) d += n;
if(d > 1) d -= n;
return d;
}
void work() {
cin>>n;
if(n == 1) { //特判 n=1
cout<<0<<'\n'; return;
}
init();
int r = 0, c = 0, r1 = 0, c1 = 0, r2 = 0, c2 = 0;
for(int i=0; i<n; i++) for(int j=0; j<n; j++) {
int x; cin>>x;
if(x == 1) r = i, c = j;
if(x == 2) r1 = i, c1 = j;
if(x == n+1) r2 = i, c2 = j;
}
r1 -= r; r2 -= r; c1 -= c; c2 -= c;
r1 = cg(r1); r2 = cg(r2); c1 = cg(c1); c2 = cg(c2); //找到方向向量 A 和 B
int id = 0;
for(int i=0; i<8; i++) { //匹配对应的 id
if(r1==ids[i][0] && c1==ids[i][1] && r2==ids[i][2] && c2==ids[i][3]) {
id = i; break;
}
}
cout<<bfs({id, r, c})<<'\n';
}
int main() {
fastio;
freopen("magic.in", "r", stdin);
freopen("magic.out", "w", stdout);
cin>>T;
while(T--) { work(); }
return MIKU;
}

浙公网安备 33010602011771号