N-Queen 문제는
크기가 N × N인 체스판 위에 퀸 N개를 서로 공격할 수 없게 놓는 문제이다.N이 주어졌을 때, 퀸을 놓는 방법의 수를 구하는 프로그램을 작성하시오.
문제 : BOJ 9663
가장 단순한 생각으로 시작했다.
N * N 크기의
Bool type
2차원 배열을 만들어서
퀸을 놓을 수 없는 곳을 모두 표시하자
백트래킹을 이용한 완전 탐색으로 접근하였다.
2중 for문으로 배열을 순회해서
퀸을 놓을 수 있다면 퀸을 놓는다.
n = 4 인 경우
0 0 0 0 1 0 0 0
0 0 0 0 → 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
퀸이 이동할 수 있는 모든 곳의 값을 False로 갱신한 뒤
새로운 리스트에 좌표 튜플을 저장한다.
1 0 0 0 1 1 1 1
0 0 0 0 → 1 1 0 0
0 0 0 0 1 0 1 0
0 0 0 0 1 0 0 1
이후 탐색이 종료되면 좌표 튜플을 가진 리스트를 이용해서
탐색 전 False로 갱신한 곳의 좌표를 다시 True로 바꿔주었다.
1 1 1 1 0 0 0 0
1 1 0 0 → 0 0 0 0
1 0 1 0 0 0 0 0
1 0 0 1 0 0 0 0
결과 : Python3 시간 초과
퀸을 놓고, 이동할 수 있는 모든 좌표를 체크하는 것이 비효율적이라고 판단되어
다른 방법을 구상해 보았다.
visited_col = [0 for _ in range(n)]
# col에 퀸이 위치했을 경우 아래와 같이 방문 체크를 해준다.
# visited_col[col] = 1
대각선
row와 col을 이용해서 대각선 상의 좌표를 사용할 수 있는지 체크해야 한다.
대각선의 종류는 두가지로 구분 할 수 있다.
1️⃣ : 왼쪽 위에서 오른쪽 아래로 향하는 대각선 ➘
2️⃣ : 왼쪽 아래에서 오른쪽 위로 향하는 대각선 ➚
1️⃣번 대각선의 경우, 열이 증가하면 행도 증가하는 y = x + a 그래프이다.
따라서 row-col
값이 같다는 사실을 확인할 수 있다.
0 | 1 | 2 | 3 | 4 | 5 | |
---|---|---|---|---|---|---|
0 | ( 0 ) | (-1) | (-2) | (-3) | (-4) | (-5) |
1 | ( 1 ) | ( 0 ) | (-1) | (-2) | (-3) | (-4) |
2 | ( 2 ) | ( 1 ) | ( 0 ) | (-1) | (-2) | (-3) |
3 | ( 3 ) | ( 2 ) | ( 1 ) | ( 0 ) | (-1) | (-2) |
4 | ( 4 ) | ( 3 ) | ( 2 ) | ( 1 ) | ( 0 ) | ( -1 ) |
5 | ( 5 ) | ( 4 ) | ( 3 ) | ( 2 ) | ( 1 ) | ( 0 ) |
2️⃣번 대각선의 경우, 열이 증가하면 행이 감소하는 y = -x + a 그래프이다.
따라서 row+col
값이 동일하다.
0 | 1 | 2 | 3 | 4 | 5 | |
---|---|---|---|---|---|---|
0 | ( 0 ) | ( 1 ) | ( 2 ) | ( 3 ) | ( 4 ) | ( 5 ) |
1 | ( 1 ) | ( 2 ) | ( 3 ) | ( 4 ) | ( 5 ) | ( 6 ) |
2 | ( 2 ) | ( 3 ) | ( 4 ) | ( 5 ) | ( 6 ) | ( 7 ) |
3 | ( 3 ) | ( 4 ) | ( 5 ) | ( 6 ) | ( 7 ) | ( 8 ) |
4 | ( 4 ) | ( 5 ) | ( 6 ) | ( 7 ) | ( 8 ) | ( 9 ) |
5 | ( 5 ) | ( 6 ) | ( 7 ) | ( 8 ) | ( 9 ) | (10) |
다시말해, 1️⃣번 대각선은 row-col
값으로,
2️⃣번 대각선은 row+col
값으로 특정할 수 있다.
단, row-col은 음수가 나올 수 있기 때문에
index로 사용할 경우 row-col+N
을 이용하고,
이때 두 대각선에서 나올 수 있는 index의 최대값은 (n-1)+n이므로
배열의 길이는 2 * n
으로 선언한다.
visited_diagonal_1 = [0 for _ in range(2*n)]
visited_diagonal_2 = [0 for _ in range(2*n)]
# (row, col)에 퀸이 위치할 경우 아래와 같이 방문 체크를 해준다.
# visited_diagonal_1[row-col+n] = 1
# visited_diagonal_2[row+col] = 1
이로서, 퀸을 위치시킴과 동시에
행
, 열
, 좌-우 대각선
모두 반복문 없이 방문체크를 할 수 있게 되었다.
[ 전체 코드 ]
import sys
n = int(sys.stdin.readline().rstrip())
visited_col = [0 for _ in range(n)]
visited_diagonal_1 = [0 for _ in range(n*2)]
visited_diagonal_2 = [0 for _ in range(n*2)]
count = 0
def solution(row):
global count
if row == n:
count += 1
return
for col in range(n):
if visited_col[col] == 0 and visited_diagonal_1[row+col] == 0 and visited_diagonal_2[row-col+n] == 0:
visited_col[col] = 1
visited_diagonal_1[row+col] = 1
visited_diagonal_2[row-col+n] = 1
solution(row+1)
visited_col[col] = 0
visited_diagonal_1[row+col] = 0
visited_diagonal_2[row-col+n] = 0
solution(0)
sys.stdout.write(count)
결과 : Python3 시간 초과..?
여기서 탐색 시간을 어떻게 더 줄일 수 있을까?
첫번째 행의 열을 절반만 탐색한다.
if n % 2 == 0:
열의 개수가 2로 나누어 떨어지므로
첫 행의 한 쪽 절반을 탐색하며 계산된 경우의 수는
첫 행의 다른 한 쪽 절반을 탐색하며 계산되는 경우의 수와 같다.
따라서, 전체 경우의 수는 첫 행의 한 쪽 절반을 탐색하며 계산된 경우의 수 * 2
가 된다.
if n % 2 == 1:
if n % 2 == 0:
일 때와 동일하게 계산할 경우첫 행의 한 쪽 절반을 탐색하며 계산된 경우의 수 * 2
를 구한 뒤첫 행의 가운데 열을 선택한 후 탐색하며 계산된 경우의 수
를 구해 더해준다.[ 전체 코드 ]
import sys
N = int(sys.stdin.readline().rstrip())
visited_col = [0 for _ in range(N)]
visited_diagonal_1 = [0 for _ in range(N*2)]
visited_diagonal_2 = [0 for _ in range(N*2)]
count = 0
def solution(row, flag):
global count
if row == N:
count += 1
return
for col in range(N//2, N//2+1) if (flag is True and row==0) else range(N//2) if row==0 else range(N):
if visited_col[col] == 0 and visited_diagonal_1[row+col] == 0 and visited_diagonal_2[row-col+N] == 0:
visited_col[col] = 1
visited_diagonal_1[row+col] = 1
visited_diagonal_2[row-col+N] = 1
solution(row+1, flag)
visited_col[col] = 0
visited_diagonal_1[row+col] = 0
visited_diagonal_2[row-col+N] = 0
solution(0, False)
count *= 2
if N % 2 == 1:
solution(0, True)
sys.stdout.write(f'{count}')
결과 : Python3 성공 [ 30840kb / 20260ms ]