蛇形方阵是模拟类题目的经典入门题,核心在于按照「右 → 下 → 左 → 上」的顺序转圈填数。本文记录两道相关题目:
- 题目一 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; for(int num=1;num<n*n;){ 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)); 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 螺旋方阵
这道题的模拟写法和题目一大致相同,不再赘述。此外还有两类更优的思路:
- 递归剥层:一层一层往里剥,若目标位置在第
f 层,时间复杂度为 O(f);
- O(1) 数学法:直接用公式定位。洛谷题解里有一版合并公式,我自己也花了一个中午推导出一版,分别记录如下。
O(1) 解法一:定位到目标层再沿边计数
思路:
- 求层数
f:目标坐标到四条边距离的最小值;
- 求当前层的边长
l = n - 2(f-1),以及本层左上角的起始数字 beg = n² - l² + 1;
- 按「顶边 → 右边 → 底边 → 左边」的顺序判断目标落在哪条边,每跨过一条边就给
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> using namespace std; int main(){ int n,i,j; cin>>n>>i>>j; 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 = min({i-1, j-1, n-i, n-j});
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);
- 同一思路可以有「分步判断」和「合并公式」两种表达,后者更简洁,前者更易懂。