-
Python-9663-N-QueenProblem Solving 2020. 12. 13. 21:29
N-Queen click
- 비트 연산자를 사용하고, 대칭성을 이용해 첫째 줄에서는 절반까지 탐색하도록 해서 결국 풀 수 있었다. 유독 시간제한이 심하게 빡빡한 것 같다.
크기가 N X N인 체스판 위에 퀸 N 개를 공격할 수 없도록 배치하는 문제이다.
처음 문제를 풀 때, 배열로 체스판을 실제로 만든 다음에 퀸을 가능한 자리에 배치하고 그 자리에서 공격할 수 있는 구역들을 모두 표시해 주는 식으로 풀었다.
하지만, 당연하게도 시간 초과가 발생해서 다른 방법을 찾아 풀어야 했다.위의 그림과 같이 퀸을 특정 칸에 배치하면, 해당 칸으로부터 가로, 세로 그리고 대각선에 퀸을 놓을 수 없게 된다.
이때, 퀸을 배치한 자리로부터 한 칸씩 아래 칸으로 내려갈 때마다 대각선 성분들이 한 칸씩 좌우로 멀어져 가는 것을 이용했다.
두 개의 퀸을 한 줄에 배치할 수 없음으로, 한 줄에는 한개의 퀸만 배치 가능하다!
한 줄 한 줄 내려가며 퀸을 배치할 수 있는 공간을 탐색해보자.
- 퀸을 특정 칸예 배치하면 그 칸으로부터 수직인 칸들, 왼쪽 아래 대각선 그리고 오른쪽 아래 대각선에 다른 퀸을 배치할 수 없게 된다.
- 배치한 칸으로부터 한 칸씩 아래로 내려오게 되면, 왼쪽 대각선 성분과 오른쪽 대각선 성분이 양옆으로 한 칸씩 멀어진다.
- 이를 이용해 한 줄씩 따로 떼어서 퀸을 배치할 수 있는 공간을 찾는다.
- 퀸을 배치한 곳으로부터 왼쪽, 오른쪽 대각선 그리고 중앙의 수직인 부분들을 각각 left, right, center 배열에 저장했다.
- center 배열의 성분들은 변하지 않고, lef와 right 배열의 성분들은 한 칸씩 내려감에 따라 좌우로 한 칸씩 이동하게 된다.
def bfs(left, center, right,cnt): global answer #n개의 queen을 모두 배치했으면, 재귀 탈출 if cnt== n: answer += 1 return #left성분들은 한 칸 왼쪽으로, #right성분들은 한 칸 오른쪽으로 이동시켜 준다. for i in range(cnt): left[i] -= 1 right[i] += 1 for i in range(n): if i not in left and check[i] and i not in right: check[i]=False #다음칸으로 이동 bfs(left+[i], center+[i], right+[i], cnt+1) check[i]=True return
- 다음과 같이 left, center 그리고 right에 퀸을 배치할 수 없는 칸들의 정보를 담았다.
- left와 right는 한 칸씩 아래로 이동함에 따라 좌우로 한 칸씩 이동시켜 주었다.
CODE
import sys read=sys.stdin.readline def bfs(left, center, right,cnt): global answer #n개의 queen을 모두 배치했으면, 재귀 탈출 if cnt== n: answer += 1 return #left성분들은 한 칸 왼쪽으로, #right성분들은 한 칸 오른쪽으로 이동시켜 준다. for i in range(cnt): left[i] -= 1 right[i] += 1 for i in range(n): if i not in left and check[i] and i not in right: check[i]=False #다음칸으로 이동 bfs(left+[i], center+[i], right+[i], cnt+1) check[i]=True return n = int(input()) answer = 0 check=list(True for _ in range(n)) bfs([], [], [],0) print(answer)
개선 1 - 비트 연산자 사용
- 비트연산자가 아직 생소하다면 11723-집합문제를 먼저 풀어보자.
비트 연산자를 사용해 시간 복잡도를 줄일 수 있다.
아래와 같이 이진수에 해당 칸에 퀸을 배치할 수 있는지 없는지 저장하자.
예를 들어서 (0,2)에 Queen이 배치된다면, left, center, right에 (1 << 2)가 더해진다.
한 칸씩 아래로 이동함에 따라 'left = left << 1', 'right = right >> 1'이 되어 양옆으로 한 칸씩 1이 밀려나게 된다.(1이 위치하는 자리가 퀸을 배치할 수 없는 곳이다.)- 설명을 위해 2차원으로 그려놨지만, 한 줄씩 탐색해 나간다는 것을 잊지 말자.
CODE - 비트연산자 사용
import sys read=sys.stdin.readline def dfs(left, center, right,cnt): global answer if cnt== n: answer += 1 return #한 칸 내려왔음으로 #왼쪽 대각선, 오른쪽 대각선 성분을 양옆으로 한 칸씩 이동시킨다. right >>= 1 left <<= 1 #center,right,left를 합쳐, #현재 줄에서 퀸을 배치할 수 없는 칸을 구한다. board = center | right | left #배치할 수 있는 곳이 없으면 탐색을 종료한다. if board&full==full: return for i in range(n): #i 번째 칸에 퀸을 놓을 예정이다. bit = 1 << i if not(board & bit): #퀸을 배치한 자리에 1을 추가하고 다음 칸으로 넘어간다. dfs(left | bit , center | bit , right | bit , cnt+1) return answer = 0 n = int(input()) full = (1<<n)-1 #칸이 가득 차있는 경우를 확인하기 위한 변수 dfs(0,0,0,0) print(answer)
개선 2 - 대칭성 이용
비트 연산자를 써도 시간 초과가 발생했다.
마지막 방법으로 대칭성을 이용해 첫 줄에서 퀸의 위치를 절반만 탐색하도록 했다.위의 그림에서 1번 칸에 퀸이 놓였을 때 체스판에 퀸을 배치할 수 있는 경우의 수와 2번 칸에 퀸이 놓였을 때의 경우의 수는 동일하다. 좌우 대칭임으로 항상 동일하다고 할 수 있다. 같은 이유로 2와 5, 3과 4에 퀸이 놓였을 때도 경우의 수가 동일하다.
따라서, 첫 줄에 한해서 퀸의 위치를 절반만 탐색하고 이를 통해 얻은 경우의 수에 두 배를 해주면 퀸을 배치할 수 있는 경우의 수를 얻을 수 있다.
n이 홀수일 때는 계산을 한 번 더 해주어야 한다.
n이 짝수일 때와 마찬가지로 1과 5, 2와 4는 경우의 수가 동일하지만 3이 남는다.
따라서 홀수일 때는 첫 줄 중앙에 위치한 칸에 퀸이 위치할 때의 경우의 수를 추가로 더해주어야 한다.CODE - 대칭성 이용
import sys read=sys.stdin.readline #첫째 줄의 (0-n//2)번째 칸 탐색 def dfs(left, center, right,cnt): global answer if cnt== n: #대칭성을 이용해 반절만 탐색하고 있음으로 answer에는 2배로 더해준다. answer += 2 return left >>= 1 right <<= 1 board = center | right | left if board&full==full: return if cnt ==0: for i in range(n//2): bit = 1 << i if not(board & bit): dfs(left | bit , center | bit , right | bit , cnt+1) return for i in range(n): bit = 1 << i if not(board & bit): dfs(left | bit , center | bit , right | bit , cnt+1) return #홀수인 경우에 n//2번째 칸을 따로 탐색해서 더해준다. def dfs_odd(left, center, right,cnt): global answer if cnt== n: answer += 1 return left >>= 1 right <<= 1 board = center | right | left if board&full==full: return if cnt ==0: bit = 1 << (n//2) if not(board & bit): dfs_odd(left | bit , center | bit , right | bit , cnt+1) return for i in range(n): bit = 1 << i if not(board & bit): dfs_odd(left | bit , center | bit , right | bit , cnt+1) return answer = 0 n = int(input()) full = (1<<n)-1 dfs(0,0,0,0) if n%2 == 0: print(answer) #홀수인 경우 dfs_odd로 n//2번째 칸의 경우의 수를 추가로 더한다. else: dfs_odd(0,0,0,0) print(answer)
CODE - C++
#include<iostream> #include<vector> #include<string> using namespace std; int N = 0, answer = 0; //퀸을 놓을 수 있는 칸을 한줄씩 탐색 //재귀함수로 한칸 더 깊이 들어가면 다음 줄로 이동했다는 의미이다. void queen(int cnt, vector<int> left, vector<int> center, vector<int> right) { if (cnt == N) { //N개를 모두 배치했음으로 경우의 수에 1 추가 answer++; return; } //좌하단 대각선 성분은 왼쪽으로 한 칸씩 밀어준다. for (int i = 1; i < N; i++) { left[i - 1] = left[i]; } left[N - 1] = 0; //우하단 대각선 성분은 오른쪽으로 한 칸씩 밀어준다. for (int i = N-1; i >0 ; i--) { right[i] = right[i-1]; } right[0] = 0; //left,right,center에 흩어진 정보를 combine에 합친다. vector<int> combine(N); for (int i = 0; i < N; i++) { if (left[i] || right[i] || center[i]) { combine[i] = 1; } } //해당 줄의 0부터 N칸까지 탐색하며 퀸을 배치한다. for (int i = 0; i < N; i++) { if (combine[i] == 0) { left[i] = 1; center[i] = 1; right[i] = 1; queen(cnt + 1, left, center, right); //이번 칸에 배치하지 않고 다음 칸에 배치하는 경우도 생각해야 함으로 //다시 0으로 reset해준다. left[i] = 0; center[i] = 0; right[i] = 0; } } } int main() { cin >> N; //vector원소가 0인 칸에만 queen배치 가능 vector<int> left(N), center(N), right(N); queen(0, left, center, right); cout << answer; }
틀린 부분이 있을 수 있습니다. 피드백 주시면 고치도록 하겠습니다! 감사합니다.👍
[꼭 다시 풀어보기]
'Problem Solving' 카테고리의 다른 글
Python-14852-타일 채우기3 (1) 2020.12.15 Python-2133-타일 채우기 (0) 2020.12.15 C++-1181-단어 정렬 (0) 2020.12.15 Python-2751-수 정렬하기2 (0) 2020.12.14 Python-2468-안전영역 (0) 2020.12.13