ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Python-9663-N-Queen
    Problem Solving 2020. 12. 13. 21:29

    N-Queen click

    Crepe

    • 비트 연산자를 사용하고, 대칭성을 이용해 첫째 줄에서는 절반까지 탐색하도록 해서 결국 풀 수 있었다. 유독 시간제한이 심하게 빡빡한 것 같다.

    크기가 N X N인 체스판 위에 퀸 N 개를 공격할 수 없도록 배치하는 문제이다.

    처음 문제를 풀 때, 배열로 체스판을 실제로 만든 다음에 퀸을 가능한 자리에 배치하고 그 자리에서 공격할 수 있는 구역들을 모두 표시해 주는 식으로 풀었다.
    하지만, 당연하게도 시간 초과가 발생해서 다른 방법을 찾아 풀어야 했다.

    Crepe

    위의 그림과 같이 퀸을 특정 칸에 배치하면, 해당 칸으로부터 가로, 세로 그리고 대각선에 퀸을 놓을 수 없게 된다.

    이때, 퀸을 배치한 자리로부터 한 칸씩 아래 칸으로 내려갈 때마다 대각선 성분들이 한 칸씩 좌우로 멀어져 가는 것을 이용했다.

    두 개의 퀸을 한 줄에 배치할 수 없음으로, 한 줄에는 한개의 퀸만 배치 가능하다!

    한 줄 한 줄 내려가며 퀸을 배치할 수 있는 공간을 탐색해보자.

    Crepe

    • 퀸을 특정 칸예 배치하면 그 칸으로부터 수직인 칸들, 왼쪽 아래 대각선 그리고 오른쪽 아래 대각선에 다른 퀸을 배치할 수 없게 된다.
    • 배치한 칸으로부터 한 칸씩 아래로 내려오게 되면, 왼쪽 대각선 성분과 오른쪽 대각선 성분이 양옆으로 한 칸씩 멀어진다.
    • 이를 이용해 한 줄씩 따로 떼어서 퀸을 배치할 수 있는 공간을 찾는다.
    • 퀸을 배치한 곳으로부터 왼쪽, 오른쪽 대각선 그리고 중앙의 수직인 부분들을 각각 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이 위치하는 자리가 퀸을 배치할 수 없는 곳이다.)

    Crepe

    • 설명을 위해 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 - 대칭성 이용

    비트 연산자를 써도 시간 초과가 발생했다.
    마지막 방법으로 대칭성을 이용해 첫 줄에서 퀸의 위치를 절반만 탐색하도록 했다.

    Crepe

    위의 그림에서 1번 칸에 퀸이 놓였을 때 체스판에 퀸을 배치할 수 있는 경우의 수와 2번 칸에 퀸이 놓였을 때의 경우의 수는 동일하다. 좌우 대칭임으로 항상 동일하다고 할 수 있다. 같은 이유로 2와 5, 3과 4에 퀸이 놓였을 때도 경우의 수가 동일하다.

    따라서, 첫 줄에 한해서 퀸의 위치를 절반만 탐색하고 이를 통해 얻은 경우의 수에 두 배를 해주면 퀸을 배치할 수 있는 경우의 수를 얻을 수 있다.

    Crepe

    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
Designed by Tistory.