洛谷B3940 [GESP樣題 四級] 填幻方

Tomorrowland_D發表於2024-07-26

題目連結:https://www.luogu.com.cn/record/168775339

題目敘述:

[GESP樣題 四級] 填幻方

題目描述

在一個 N×N 的正方形網格中,每個格子分別填上從 1 到 N×N 的正整數,使得正方形中任一行、任一列及對角線的幾個數之和都相等,則這種正方形圖案就稱為“幻方”(輸出樣例中展示了一個 3×3 的幻方)。我國古代稱為“河圖”、“洛書”,又叫“縱橫圖”。

幻方看似神奇,但當 N 為奇數時有很方便的填法:

  1. 一開始正方形中沒有填任何數字。首先,在第一行的正中央填上 1。
  2. 從上次填數字的位置向上移動一格,如果已經在第一行,則移到同一列的最後一行;再向右移動一格,如果已經在最右一列,則移動至同一行的第一列。如果移動後的位置沒有填數字,則把上次填寫的數字的下一個數字填到這個位置。
  3. 如果第 2 步填寫失敗,則從上次填數字的位置向下移動一格,如果已經在最下一行,則移到同一列的第一行。這個位置一定是空的(這可太神奇了!)。把上次填寫的數字的下一個數字填到這個位置。
  4. 重複 2、3 步驟,直到所有格子都被填滿,幻方就完成了!

快來編寫一個程式,按上述規則,製作一個 N×N 的幻方吧。

輸入格式

輸入為一個正奇數 N,保證 3<=N<=21。

輸出格式

輸出 N 行,每行 N 個空格分隔的正整數,內容為 N×N 的幻方。

樣例 #1

樣例輸入 #1

3

樣例輸出 #1

8 1 6
3 5 7
4 9 2

思路:

這題我們直接根據題意模擬就可以了:

唯一要注意的點就是: 如果已經在第一行,則移到同一列的最後一行再向右移動一格,如果已經在最右一列,則移動至同一行的第一列,這兩句話怎麼實現呢?我們要設定四個邏輯判斷語句嗎?

其實不用,我們使用取模運算,一句話便可以達到這條語句的效果!

取模運算

取模運算廣泛應用於有邊界的圖形當中,尤其是環形或者矩形這種的含邊界的處理問題,比如:如果觸碰到了已經到了最上面,我們就移動到最下面碰到這種需求時,就體現出了取模運算的重要性!

我們先了解一下取模運算的性質:

取模運算的性質:

在C++(以及許多其他程式語言)中,取模運算(也稱為模除、求餘運算)是一個重要的算術操作,它用來求得兩個數相除後的餘數。在C++中,取模運算通常使用 % 符號表示。瞭解取模運算的性質對於

編寫準確和高效的程式碼至關重要。以下是C++中取模運算的一些基本性質:

  1. 定義:如果 a 是被除數,n 是除數(n 不為0),那麼 a % n 的結果是 a 除以 n 的餘數。注意,這裡的結果的符號與被除數 a 的符號相同。

  2. 結果範圍:對於整數 a 和正整數 n,a % n 的結果是一個在 0 到 n-1 之間的整數(包括 0 和 n-1)。

  3. 與負數的關係:當除數是正數時,被除數為負數時,結果的符號與被除數相同。然而,不同程式語言和編譯器在實現取模運算時,對負除數的處理可能有所不同。在C++中,如果除數是負數,結果將依賴於具體的編譯器實現,但通常不保證跨所有平臺的一致性。

  4. 週期性:取模運算具有周期性。對於任何整數 a 和正整數 n,序列 a % n, (a+1) % n, (a+2) % n, ... 會以 n 為週期重複。這個性質在解決諸如迴圈陣列索引、雜湊表衝突解決等問題時非常有用。

  5. 分配律不成立:與乘法運算不同,取模運算不滿足分配律,即 (a+b) % n 不一定等於 a % n + b % n。但是,有一個類似的性質,即 (a+b) % n = ((a % n) + (b % n)) % n,這在進行取模運算時非常有用,尤其是在處理大數時,可以減少中間結果的規模。
    結合律成立:雖然分配律不成立,但取模運算滿足結合律,即 (a % n) % m 等於 a % (n * m)(當 n 和 m 互質時)。這個性質在最佳化計算或處理複雜表示式時很有用。

因此,在C++中,如果對一個負數做取模運算,結果是不確定的,我們得額外處理這個邏輯,就拿這道題舉例子,我們假設當前處於的行為curRow,經過變換後的行數為newRow,(向上移動一行)我們如果不是第一行的

話,直接寫出以下式子: newRow=(curRow-1)%n;,但是curRow=0時,curRow-1就為負數了!取模的結果就變成未知了!因此我們需要對這個式子額外處理,變成newRow=(curRow-1+n)%n就可以保證我們的

運算的答案在0-n-1之間。

思路:

經過上面的講解,相信大家對取模運算就有了基本的認識了,那麼我們直接上程式碼:


#include<iostream>
using namespace std;
//全域性變數會自動初始化為0
int a[22][22];
int main()
{
	int n; cin >> n;
	int i = 1;
	int curRow = 0, curCol = n / 2;
	while (i <= n * n) {
		if (a[curRow][curCol] == 0) {
			a[curRow][curCol] = i;
			//進行步驟二
			int newRow = (curRow - 1 + n) % n;
			int newCol = (curCol + 1) % n;
			//步驟二不成立
			if (a[newRow][newCol] != 0) {
				newRow = (curRow + 1) % n;
				newCol = curCol;
			}
			//更新當前所在行數和當前所在列數
			curRow = newRow;
			curCol = newCol;
			i++;
		}
	}
	for (int i = 0; i < n; i++) {
		for (int j = 0; j < n; j++) {
			cout << a[i][j] << " ";
		}
		cout << endl;
	}
	return 0;
}