蛇形方阵是模拟类题目的经典入门题,核心在于按照「右 → 下 → 左 → 上」的顺序转圈填数。本文记录两道相关题目:

  • 题目一 P5731:两种经典模拟写法;
  • 题目二 P2239:在模拟之外,更进一步的 O(1) 数学解法。

蛇形方阵示意图

题目一:洛谷 P5731 蛇形方阵

题目链接:P5731 【深基5.习6】蛇形方阵

方法一:4 个 while 循环

用四个 while 分别控制「右、下、左、上」四个方向的填充。每个方向一直走,直到越界或格子已有值,然后回退一格、转向 90°。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include<iostream>
#include<vector>
#include<cstdio>
using namespace std;
int main(){
int n;
cin>>n;
vector<vector<int>> a(n+1,vector<int>(n+1,0));

int y=0,x=0;
//y行x列
for(int num=1;num<n*n;){
//向右,x<n保证不超边界【0,n-1】,并保证填入的地方为空
while(x<n&&a[y][x]==0){
a[y][x]=num;
num++;
x++;
}
x--;
y++;

while(y<n&&a[y][x]==0){//向下
a[y][x]=num;
num++;
y++;
}
y--;
x--;

while(x>=0&&a[y][x]==0){//向左
a[y][x]=num;
num++;
x--;
}
x++;
y--;

while(y>=0&&a[y][x]==0){//向上
a[y][x]=num;
num++;
y--;
}
y++;
x++;
}

for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
printf("%3d",a[i][j]);
}
cout<<endl;
}


return 0;
}

方法二:方向数组 + 循环 n×n 次

用 dx/dy 数组统一表示四个方向:每次先填当前格子,再预判下一格,一旦越界或已被占用就顺时针转向。代码结构更统一,也更容易扩展到更复杂的模拟。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include<cstdio>
#include<vector>
using namespace std;
int main(){
int n;
scanf("%d",&n);
int x=0,y=0;
vector<vector<int>> a(n,vector<int>(n,0));

//0右,1下,2左,3上 顺时针
int temp=0;
int dx[4]={1,0,-1,0};
int dy[4]={0,1,0,-1};

for(int i=1;i<=n*n;i++){

a[y][x]=i;

int nx=x+dx[temp];
int ny=y+dy[temp];
//当到达边界时,或者下一步空位有值时,进到下一步
if(nx>=n||ny>=n||nx<0||ny<0||a[ny][nx]!=0){
temp++;
temp%=4;
nx=x+dx[temp];
ny=y+dy[temp];
}
x=nx;
y=ny;
}

for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
printf("%3d",a[i][j]);
}
printf("\n");
}

return 0;
}

两种写法对比

对比项 方法一:4 个 while 方法二:方向数组
代码结构 四段重复逻辑,靠回退坐标转向 统一循环,逻辑清晰
上手难度 直观,容易想到 需要理解方向数组
扩展性 加方向要再写一段 改数组即可,适合更复杂的模拟

题目二:洛谷 P2239 螺旋方阵

题目链接:P2239 螺旋方阵

这道题的模拟写法和题目一大致相同,不再赘述。此外还有两类更优的思路:

  1. 递归剥层:一层一层往里剥,若目标位置在第 f 层,时间复杂度为 O(f);
  2. O(1) 数学法:直接用公式定位。洛谷题解里有一版合并公式,我自己也花了一个中午推导出一版,分别记录如下。

O(1) 解法一:定位到目标层再沿边计数

思路:

  1. 求层数 f:目标坐标到四条边距离的最小值;
  2. 求当前层的边长 l = n - 2(f-1),以及本层左上角的起始数字 beg = n² - l² + 1;
  3. 按「顶边 → 右边 → 底边 → 左边」的顺序判断目标落在哪条边,每跨过一条边就给 beg 累加 l-1。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include<iostream>
#include<algorithm> //min
using namespace std;
int main(){
int n,i,j;
cin>>n>>i>>j;

//求层数f:到四条边界最小距离
int f = min( min(i, n-i+1), min(j, n-j+1) );

int l = n - 2*(f-1); //当前这一层正方形边长
int beg = n*n - l*l + 1; //本层左上角起始数字

if(i == f){
//第一边:顶边,向右
cout << beg + (j - f);
return 0;
}
beg += l-1;
if(j == n-f+1){
//第二边:右边,向下
cout << beg + (i - f);
return 0;
}
beg += l-1;
if(i == n-f+1){
//第三边:底边,向左
cout << beg + (n-f+1 - j);
return 0;
}
beg += l-1;
//第四边:左边,向上
cout << beg + (n-f+1 - i);
return 0;
}

⚠️ 大数据范围注意:以上代码使用 int,仅满足洛谷 P2239 的数据范围。蓝桥杯 2026 萌新赛的迎新座位编号中 N ≤ 10^9,n*n 会超出 int 上限,必须把 n、l、beg 等变量改成 long long。

O(1) 解法二:合并公式

洛谷题解里还有一个 O(1) 写法,思路和解法一很像,但把四条边合并成了两个数学公式,代码简洁不少:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include<iostream>
#include<algorithm>
using namespace std;
int main(){
int n,i,j,x;
cin>>n>>i>>j;

// x:从0开始计数的圈号,点(i,j)距离四条边界的最小距离
// min({i-1, j-1, n-i, n-j})
// i-1:到顶部距离;j-1:到左侧距离
// n-i:到底部距离;n-j:到右侧距离
x = min({i-1, j-1, n-i, n-j});

// 判断:是否落在【上边】或者【右边】
// 上边:i == x+1,当前圈的第一行
// 右边:j == n-x,当前圈的最后一列
if(i == x+1 || j == n-x){
// 上边、右边 合并公式
cout << 4*x*n - 4*x*x - 2*x + i + j - 1;
}
else{
// 剩下两个边:【下边】或者【左边】合并公式
cout << 4*x*n - 4*x*x - 6*x + 4*n - i - j - 1;
}
return 0;
}

合并公式的完整推导过程见原作者题解:P2239 题解 - 洛谷。

小结

  • 蛇形方阵的模拟写法中,方向数组比多个 while 更通用,是更推荐的写法;
  • 当只需要查询某个位置的值(而非输出整个方阵)时,可以用分层思想把时间复杂度从 O(n²) 直接降到 O(1);
  • 同一思路可以有「分步判断」和「合并公式」两种表达,后者更简洁,前者更易懂。