題目連結:https://www.luogu.com.cn/record/168775339
題目敘述:
[GESP樣題 四級] 填幻方
題目描述
在一個 N×N 的正方形網格中,每個格子分別填上從 1 到 N×N 的正整數,使得正方形中任一行、任一列及對角線的幾個數之和都相等,則這種正方形圖案就稱為“幻方”(輸出樣例中展示了一個 3×3 的幻方)。我國古代稱為“河圖”、“洛書”,又叫“縱橫圖”。
幻方看似神奇,但當 N 為奇數時有很方便的填法:
- 一開始正方形中沒有填任何數字。首先,在第一行的正中央填上 1。
- 從上次填數字的位置向上移動一格,如果已經在第一行,則移到同一列的最後一行;再向右移動一格,如果已經在最右一列,則移動至同一行的第一列。如果移動後的位置沒有填數字,則把上次填寫的數字的下一個數字填到這個位置。
- 如果第 2 步填寫失敗,則從上次填數字的位置向下移動一格,如果已經在最下一行,則移到同一列的第一行。這個位置一定是空的(這可太神奇了!)。把上次填寫的數字的下一個數字填到這個位置。
- 重複 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++中取模運算的一些基本性質:
-
定義:如果 a 是被除數,n 是除數(n 不為0),那麼 a % n 的結果是 a 除以 n 的餘數。注意,這裡的結果的符號與被除數 a 的符號相同。
-
結果範圍:對於整數 a 和正整數 n,a % n 的結果是一個在 0 到 n-1 之間的整數(包括 0 和 n-1)。
-
與負數的關係:當除數是正數時,被除數為負數時,結果的符號與被除數相同。然而,不同程式語言和編譯器在實現取模運算時,對負除數的處理可能有所不同。在C++中,如果除數是負數,結果將依賴於具體的編譯器實現,但通常不保證跨所有平臺的一致性。
-
週期性:取模運算具有周期性。對於任何整數 a 和正整數 n,序列 a % n, (a+1) % n, (a+2) % n, ... 會以 n 為週期重複。這個性質在解決諸如迴圈陣列索引、雜湊表衝突解決等問題時非常有用。
-
分配律不成立:與乘法運算不同,取模運算不滿足分配律,即 (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;
}