题解:洛谷 AT_abc436_b Magic Square
【题目来源】
洛谷:AT_abc436_b [ABC436B] Magic Square - 洛谷
【题目描述】
给定一个奇数 \(N\),且 \(N\geq 3\)。
有一个 \(N\) 行 \(N\) 列的网格,初始时所有格子都是空的。现在,你将按照如下步骤在每个格子里写入整数。令 \((i,j)\) 表示第 \(i+1\) 行(自顶向下)第 \(j+1\) 列(自左向右)的格子(\(0\leq i<N, 0\leq j<N\))。
- 在格子 \((0,\frac{N-1}{2})\) 中写入 \(1\)。
- 重复以下操作 \(N^2-1\) 次:
- 设 \((r,c)\) 是上一次写入整数的格子,\(k\) 是上一次写入的整数。如果格子 \(((r-1)\bmod N, (c+1)\bmod N)\) 为空,则在该格子写入 \(k+1\);否则,在格子 \(((r+1)\bmod N, c)\) 中写入 \(k+1\)。其中 \(x\bmod N\) 表示 \(x\) 除以 \(N\) 的余数。
请找出这个过程中每个格子里最终写入的整数。可以证明每个格子恰好只会被写入一个整数。
【输入】
输入从标准输入按以下格式给出:
\(N\)
【输出】
设 \(a_{i,j}\) 表示第 \((i,j)\) 个格子里写入的整数,输出格式如下:
\(a_{0,0}\ a_{0,1}\ \dots\ a_{0,N-1}\)
\(a_{1,0}\ a_{1,1}\ \dots\ a_{1,N-1}\)
\(\vdots\)
\(a_{N-1,0}\ a_{N-1,1}\ \dots\ a_{N-1,N-1}\)
【输入样例】
3
【输出样例】
8 1 6
3 5 7
4 9 2
【核心思想】
-
问题分析:给定奇数 \(N\),需要按照特定规则构造 \(N \times N\) 的幻方(Magic Square)。规则为:从第一行中间开始填 \(1\),之后每次尝试往当前格子的右上角填下一个数;若右上角已被占用或越界(通过取模循环处理),则改为填在当前格子的正下方。这是一个直接模拟/规则构造问题,关键在于正确实现坐标变换和边界循环。
-
算法选择:
- 直接模拟:严格按照题目给定的构造规则,逐步填入 \(1\) 到 \(N^2\) 的整数
- 循环边界处理:使用 \((x + N) \% N\) 实现越界时的循环回绕(如第 \(-1\) 行变为第 \(N-1\) 行)
-
关键步骤:
- 初始化:创建 \(N \times N\) 的空矩阵 \(a\),初始值全为 \(0\)
- 放置 \(1\):\(a[0][(N-1)/2] = 1\),当前位置 \((i, j) = (0, (N-1)/2)\)
- 循环放置(\(k\) 从 \(1\) 到 \(N^2 - 1\)):
- 计算候选位置(右上角):\(next\_i = (i - 1 + N) \% N\),\(next\_j = (j + 1) \% N\)
- 判断分支:
- 若 \(a[next\_i][next\_j] = 0\)(空):填入 \(k+1\),更新 \((i, j) = (next\_i, next\_j)\)
- 若已被占用:填入 \(a[(i+1) \% N][j] = k+1\),更新 \(i = (i+1) \% N\)(正下方)
- \(k \mathrel{+}= 1\)
- 输出矩阵:按行优先输出 \(a\)
-
时间/空间复杂度:
- 时间复杂度:\(O(N^2)\),恰好填入 \(N^2\) 个数,每个数 \(O(1)\) 计算位置
- 空间复杂度:\(O(N^2)\),存储 \(N \times N\) 的幻方矩阵
-
规则模拟的核心思想:
- Siamese Method(暹罗法):该构造规则是经典的奇数阶幻方生成算法,保证每行、每列、对角线之和相等
- 循环坐标系:通过取模运算将二维网格视为环面(torus),消除边界特殊性,统一处理所有位置
- 贪心占位:始终优先尝试右上角,仅在冲突时回退到正下方,确保 \(N^2\) 个位置恰好被填满且不重复
- 适用于"按固定规则逐步构造矩阵/序列"类问题,核心是准确翻译题目规则为代码逻辑
【算法标签】
普及- #模拟
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int N = 105; // 最大矩阵大小
int n; // 幻方大小(奇数)
int a[N][N]; // 幻方矩阵
int main()
{
// 输入幻方大小n(应为奇数)
cin >> n;
// 1. 初始化:将1放在第一行的中间列
a[0][(n - 1) / 2] = 1;
int k = 1; // 当前已放置的数字
// 当前位置:i行,j列
int i = 0, j = (n - 1) / 2;
// 2. 放置剩余的n*n-1个数字
while (k <= n * n - 1)
{
// 计算右上角的位置(循环处理)
int next_i = (i - 1 + n) % n; // 上一行
int next_j = (j + 1) % n; // 右一列
// 如果右上角位置为空
if (a[next_i][next_j] == 0)
{
// 将下一个数字放在右上角
a[next_i][next_j] = k + 1;
// 更新当前位置
i = next_i;
j = next_j;
}
else
{
// 如果右上角被占用,放在正下方
a[(i + 1) % n][j] = k + 1;
// 更新当前位置
i = (i + 1) % n; // 下一行
j = j; // 同一列
}
k++; // 已放置数字数量加1
// 调试输出
// cout << "i j " << i << " " << j << endl;
}
// 3. 输出幻方
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
cout << a[i][j] << " ";
}
cout << endl;
}
return 0;
}
【运行结果】
3
8 1 6
3 5 7
4 9 2
浙公网安备 33010602011771号