题目描述
幻方是一种很神奇的 N×N
矩阵:它由数字 1,2,3,…,N×N
构成,且每行、每列及两条对角线上的数字之和都相同。
当 N
为奇数时,我们可以通过以下方法构建一个幻方:
- 首先将
1
写在第一行的中间。 - 之后,按如下方式从小到大依次填写每个数
K(K=2,3,…,N×N)
:- 若
(K−1)
在第一行但不在最后一列,则将K
填在最后一行,(K−1)
所在列的右一列; - 若
(K−1)
在最后一列但不在第一行,则将K
填在第一列,(K−1)
所在行的上一行; - 若
(K−1)
在第一行最后一列,则将K
填在(K−1)
的正下方; - 若
(K−1)
既不在第一行,也不在最后一列,如果(K−1)
的右上方还未填数,则将K
填在(K−1)
的右上方,否则将K
填在(K−1)
的正下方。
- 若
现给定 N
,请按上述方法构造 N×N
的幻方。
输入格式
输入只有一行,包含一个整数,即幻方的大小。
输出格式
输出包含 N
行,每行 N
个整数,即按上述方法构造出的 N×N
的幻方。相邻两个整数之间用单个空格隔开。
样例
输入数据 1
3
输出数据1
8 1 6
3 5 7
4 9 2
数据范围与提示
对于 100% 的数据,1≤N≤39
且为奇数。
奇数阶幻方的构造方法
当幻方的阶数N为奇数时,有一个非常简单且有效的方法来构造它——西塞罗(Siamese)方法或称步进法。这个方法的规则如下:
初始位置:将数字1放置在第一行的中间。
下一个数字的位置:
- 如果当前数字位于第一行但不在最后一列,则将下一个数字放在最后一行,当前列的右一列。
- 如果当前数字位于最后一列但不在第一行,则将下一个数字放在第一列,当前行的上一行。
- 如果当前数字同时位于第一行和最后一列,则将下一个数字直接放在当前数字的下方。
- 如果当前数字的右上方已经有数字或者当前数字不处于上述特定位置,则将下一个数字放在当前数字的正下方。
继续填充:按照这个规则,依次填入2到N×N的所有数字。
这种方法的美妙之处在于它的简洁和高效,只需要遵循几个简单的规则,就能够快速构造出满足条件的奇数阶幻方。
具体代码
#include<iostream>
using namespace std;
const int N = 40;
int a[N][N]; // 存储幻方阵的数组
int main(){
int n, m, x, y; cin >> n; // n为幻方阵的阶数
// 将第一个数字1放在第一行的中间
a[1][n/2+1] = 1;
x = 1;
y = n/2+1;
m = n*n; // 幻方阵中数字的总数
for(int i = 2; i <= m; i++){
int dx, dy;
// 尝试将下一个数字放在当前位置的右上角
if(x == 1 && y != n) dx = n, dy = y + 1;
else if(y == n && x != 1) dx = x - 1, dy = 1;
else if(x == 1 && y == n) dx = x + 1, dy = y;
else {
if(a[x-1][y+1] == 0) dx = x - 1, dy = y + 1;
else dx = x + 1, dy = y;
}
// 处理越界情况
if (dx < 1) dx = n;
if (dy > n) dy = 1;
if (dy < 1) dy = n;
a[dx][dy] = i;
x = dx;
y = dy;
}
// 输出幻方阵
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++){
cout << a[i][j] << " ";
}
cout << endl;
}
return 0;
}