![[백준] 1307 (마방진)](/images/boj-1307.jpeg)
[백준] 1307 (마방진)
마방진(Magic Square)이란 N×N 격자에 1부터 N²까지의 정수를 하나씩 배치하여 모든 가로줄, 세로줄, 대각선의 합이 동일한 배치를 말합니다. 이때 공통 합을 매직 상수(Magic Constant)라 하며, 그 값은 M = N(N² + 1) / 2입니다.
N의 형태에 따라 구성 방법이 달라집니다. 이 글에서는 홀수 N,4의 배수 N,4k+2 형태의 N세 가지 경우를 각각 다룹니다.
① 홀수 마방진 (N = 2k+1)
알고리즘: Siamese (de la Loubère) 방법
17세기 프랑스 외교관 Simon de la Loubère가 태국에서 배워온 방법입니다. 홀수 크기 마방진을 구성하는 가장 고전적이고 직관적인 방법으로, 단순한 규칙 두 가지만으로 완전한 마방진을 만들어냅니다.
규칙 1: 기본 이동 ↗
현재 위치에서 한 칸 위, 한 칸 오른쪽으로 이동합니다. 격자 바깥으로 나가면 반대편으로 되돌아옵니다 (토러스 구조).
규칙 2: 충돌 시 ↓
이동하려는 칸에 이미 숫자가 있으면, 대신 원래 위치에서 한 칸 아래로 이동합니다.
# 시작: 첫 행의 가운데 열에 1 배치
x, y = 0, n // 2
for i in 1 to n*n:
grid[x][y] = i
nx, ny = (x-1) % n, (y+1) % n # ↗ 이동
if grid[nx][ny] != 0: # 충돌!
x = (x+1) % n # ↓ 아래로
else:
x, y = nx, ny인터랙티브 시뮬레이터
숫자를 하나씩 배치하는 과정을 관찰해 보세요. ↗ 방향으로 이동하다가 충돌하면 ↓로 내려갑니다.
② 4N 마방진 (N = 4k)
알고리즘: 십자 교환법 (Cross-Swap)
N이 4의 배수일 때 사용하는 간결한 방법입니다. 핵심 아이디어는 1~N²을 순서대로 채운 뒤, 특정 영역의 셀을 점대칭 위치와 교환하는 것입니다.
유지 영역
4개의 코너 블록 (각 N/4 × N/4) 과 중앙 블록 (N/2 × N/2) 은 원래 값을 유지합니다.
교환 영역 (십자)
코너와 중앙 사이를 잇는 십자(+) 모양 영역의 셀 (i, j)를 점대칭 위치 (N-1-i, N-1-j)와 교환합니다.
8×8 교환 패턴: ↔ = 교환, · = 코너 유지, · = 중앙 유지
# 1~N² 순서대로 채우기
ans[i][j] = i * N + j + 1
# 십자(cross) 영역을 점대칭 교환
for i in range(N // 4): # 상단 Q행
for j in range(N//4, 3*N//4): # 중앙 열
swap(ans[i][j], ans[N-1-i][N-1-j]) # 상↔하
swap(ans[j][i], ans[N-1-j][N-1-i]) # 좌↔우왜 동작하는가?
순서대로 채운 격자에서 (i, j)의 값과 점대칭 위치 (N-1-i, N-1-j)의 값의 합은 항상 N² + 1입니다. 교환 시 두 위치를 포함하는 행·열 쌍의 합이 보존되며, 십자 패턴이 각 행·열에서 정확히 절반의 셀만 교환되도록 보장합니다.
인터랙티브 시뮬레이터
순서대로 채운 뒤, 코너·중앙은 유지하고 십자(cross) 영역만 점대칭 교환합니다.
③ 4N+2 마방진 (N = 4k+2)
알고리즘: Strachey 방법
N = 4k+2 (예: 6, 10, 14, …) 형태는 홀수도 아니고 4의 배수도 아닌 가장 까다로운 경우입니다. Strachey 방법은 n = N/2 크기의 홀수 마방진을 기반으로, 각 사분면에 서로 다른 오프셋을 더하는 합성 방법입니다.
홀수 마방진 구성
n = N/2 크기의 홀수 마방진 M을 만듭니다 (값: 1~n²).
오프셋 승수 결정
N×N의 각 셀에 0, 1, 2, 3 중 하나의 승수를 배정합니다.
최종 합산
ans[i][j] = 승수 × n² + M[i%n][j%n]으로 값을 결정합니다.
오프셋 승수 배치 규칙:
# 상반부 (i < N/2): k = [3, 0, 2, 1] # 하반부 (i ≥ N/2): k = [0, 3, 1, 2] # # 좌측 절반 (cols 0..N/2-1): # 일반 행: [0, N/4)열 → k[0], [N/4, N/2)열 → k[1] # 특수 행 (i % (N/2) == N/4): # 0열 → k[1], [1, 1+N/4)열 → k[0], 나머지 → k[1] # # 우측 절반 (cols N/2..N-1): # [N/2, N-N/4]열 → k[2], [N-N/4+1, N)열 → k[3] # # 최종: ans[i][j] = 승수[i][j] × n² + M[i%n][j%n]
왜 동작하는가?
오프셋 배치는 각 행·열에서 승수 0, 1, 2, 3이 각각 정확히 n번(= N/2번) 등장하도록 설계되었습니다. 따라서 오프셋의 행/열 합이 균일해지고, 4개 사분면에 복사된 홀수 마방진의 합 성질도 보존되므로 전체가 마방진이 됩니다.
인터랙티브 시뮬레이터
홀수 마방진을 4개 사분면에 복사한 뒤, 오프셋 패턴에 따라 값을 더합니다.
3×3 홀수 마방진
정리
세 가지 방법 요약
Siamese
O(N²)
예: 3, 5, 7, 9, …
십자 교환
O(N²)
예: 4, 8, 12, 16, …
Strachey
O(N²)
예: 6, 10, 14, 18, …
핵심 포인트
세 방법 모두 시간·공간 복잡도가 O(N²)이며, N ≤ 300 범위에서 충분히 빠릅니다. 입력 N의 형태(홀수 / 4의 배수 / 4k+2)에 따라 적절한 방법을 선택하면 됩니다.
구현
코드 구조
n = N # N은 홀수
odd = [[0] * n for _ in range(n)]
x, y = 0, n // 2 # 첫 행 가운데에서 시작
for i in range(1, n * n + 1):
odd[x][y] = i
nx = (x - 1) % n # ↗ 위로
ny = (y + 1) % n # ↗ 오른쪽으로
if odd[nx][ny] != 0: # 충돌 시
x = (x + 1) % n # 대신 ↓ 아래로
else:
x, y = nx, nySiamese 방법의 핵심 루프입니다. 각 숫자를 배치한 뒤 ↗ 이동을 시도하고, 이미 숫자가 있으면 ↓로 방향을 바꿉니다.
테스트
직접 테스트해 보기
원하는 크기를 입력하면 적절한 방법이 자동으로 선택됩니다.