• 问答
  • P2615 [NOIP 2015 提高组] 神奇的幻方为什么用int创建数组会过不了

  • @ 2026-1-21 20:13:25
#include <bits/stdc++.h>
using namespace std;
int n, k = 1, a, b;
int main ()
{
	cin >> n;
//	int c[41][41];
	vector<vector<int>> c(n+2, vector<int>(n+2, 0));
	c[1][n/2+1] = 1;
	a = 1; b = n/2+1;
	for ( int i = 2; i <= n * n; i ++)//a行 b列 
	{
		if ( a != 1 && b != n)
		{
			if ( c[a-1][b+1] == 0) 
			{
				c[a-1][b+1] = i;
				a = a-1; b = b+1;
			}
			else 
			{
				c[a+1][b] = i;
				a = a+1; b = b;
			}
		}
		else if ( a == 1 && b != n)
		{
			c[n][b+1] = i;
			a = n; b = b+1;
		}
		else if ( a != 1 && b == n)
		{
			c[a-1][1] = i;
			a = a-1; b = 1;
		}
		else if ( a == 1 && b == n)
		{
			c[a+1][b] = i;
			a = a+1;b = b;
		}
		
		//if (c[n][n/2+1] == n * n) break;
	}
	for ( int i = 1; i <= n; i ++)
	{
		for ( int j = 1; j <= n; j ++)
		{
			if ( j != 1) cout << ' ';
			cout << c[i][j];
		}
		cout << '\n';
	}
	return 0;
}

P2615 [NOIP 2015 提高组] 神奇的幻方

1 comments

  • @ 2026-1-21 20:14:06

    同学你好,我看到你上传的代码,并注意到你的问题:“为什么用int创建数组会过不了”。首先,我注意到你的代码中已经将原本注释掉的 int c[41][41] 替换为了 vector<vector<int>> c(n+2, vector<int>(n+2, 0))。这是一个很好的观察起点。

    让我先分析一下你的问题核心。题目中 n 的范围是 1 ≤ n ≤ 39 且为奇数。使用 int c[41][41] 在栈上声明数组,大小是足够的(因为 41 > 39)。所以,数组大小本身并不是导致“过不了”的直接原因

    那么,问题可能出在哪里呢?我们来一起分析:

    1. 数组初始化问题:你的 int c[41][41] 声明在 main 函数内部,是局部变量。局部数组不会自动初始化为0,其内容是未定义的随机值。而你的幻方填充逻辑中,判断 c[a-1][b+1] == 0 是核心条件。如果该位置原本是随机值而非0,你的填充顺序就会被打乱,导致结果错误。你换成 vector 后,通过 vector<int>(n+2, 0) 进行了显式初始化,这是正确的。

    2. 数组下标范围:你原来的 int c[41][41] 是固定大小的。但在后续的循环中,你访问的下标是 a-1, b+1 等。当 a=1 时,a-1=0,你访问了 c[0][...];当 b=n 时,b+1=n+1,你访问了 c[...][n+1]。对于 int c[41][41],如果 n=39n+1=40,仍在数组范围内(0~40)。但访问下标0在逻辑上可能不符合你的预期,不过C++语法上不会越界。然而,更关键的是,你的 vector 创建为 n+2 大小,这为边界情况(如 n+1)预留了空间,这是一个好习惯。

    3. 代码逻辑的潜在问题:让我们仔细审视你的条件判断分支。题目描述的规则是:

      • 若当前位置在第一行但不在最后一列,则下一个数填在最后一行、下一列。
      • 若当前位置在最后一列但不在第一行,则下一个数填在上一行、第一列。
      • 若当前位置在第一行最后一列,则下一个数填在当前位置的正下方。
      • 若当前位置既不在第一行也不在最后一列,且右上方未填数,则填在右上方;否则填在正下方。

      你的代码结构是:

      if (a != 1 && b != n) { ... }
      else if (a == 1 && b != n) { ... }
      else if (a != 1 && b == n) { ... }
      else if (a == 1 && b == n) { ... }
      

      这个逻辑覆盖了所有情况,但顺序很重要。请思考:当 a==1 && b!=n 时,进入第二个分支;当 a!=1 && b==n 时,进入第三个分支。这符合题目描述吗?题目说“第一行但不在最后一列”对应你的第二个分支,是正确的。但请再检查一下:你的第一个分支 if (a != 1 && b != n) 中,有一个内层判断 if (c[a-1][b+1] == 0),这里 a-1 可能等于0吗?(当 a=1 时不会进入这个分支,所以安全)。然而,当 a=2, b=38 (n=39) 时,c[1][39] 是右上方,但 39n 吗?b=38 不等于 n=39,所以进入第一个分支。这看起来正确。

    那么,如何验证问题所在? 我建议你进行以下调试:

    1. 如果你仍想使用 int c[41][41],请在其声明后立即用 memset(c, 0, sizeof(c)); 进行清零。然后测试是否可以通过。
    2. 检查你的输出。对于小的 n(如 n=3),你可以手动计算幻方结果,与你的程序输出对比。
    3. 关注边界:当 n=1 时,你的程序如何运行?n/2+1 对于整数除法是可行的吗?(1/2=0, 0+1=1,正确)。

    启发式提问

    • 你认为未初始化的数组会对你的程序逻辑产生什么影响?
    • 对于 n=3,你能手动模拟出正确的幻方吗?你的程序输出是否与之匹配?
    • 如果你使用 int c[41][41] 并添加 memset 初始化,你认为结果会怎样?

    请尝试上述调试步骤,并告诉我你的发现。如果问题仍然存在,我们可以进一步分析你的算法逻辑细节。记住,在竞赛编程中,局部数组的自动初始化问题是一个常见的陷阱。

    • 1