[PY] 9663 : N-Queen

 

https://www.acmicpc.net/problem/9663

결론만 적으면 내 힘으로 못 풀었다. 그래서 이렇게 별도로 글을 쓴다.
작동이 되는 방식을 두 가지 구현을 했다. 하나는 2차원 배열로, 하나는 1차원 배열로..
더 깊이 고민하면 됐을지도 모르겠지만 (정신)체력적인 한계로 책을 결국 펼치고 말았다.

분명 몇 달 전에도 비슷한 엔딩으로 모든 구조를 되새겼다고 생각하고 지나갔던 문제인데,
사람이 기억하려고 의식적으로 노력해야 하는 것 같다.

N-Queen 문제는 어쨌든 배경지식으로 체스에서 Queen이 할 수 있는 것을 알아야 한다.
퀸은 진짜 사방팔방 움직일 수 있다. 말 그대로 8 방향에서 움직일 수 있다. 상하좌우 북서 동북 남동 남서

서로 공격하지 않게 해야 한다고 하면 사전에 놓을 수 없는 범위를 설정하는 것이 좋겠는데, 이 부분에 대한 계산을 가능한한 줄여야했다.
놓을 수 없는 부분이 나올 때까지 탐색하는 것도 좋지만 영 성치 않으면 갖다 버리고 바로 다른 선택지 고려해보는 코드까지 생각을 해야 했다.

1차 실패 : 2차원 배열로 구현

원초적인 고민만 했다. 퀸이 배치되면 놓을 수 없는 구역을 모두 표시해둔다.
다음 퀸은 놓을 수 없는 구역을 피해서 배치된다. 퀸을 더 이상 놓을 수 없다고 판단된 경우 배치된 퀸의 갯수에 따라 할 일이 달라진다.

# N queen
import sys

sys.setrecursionlimit(2000)

# 퀸은 8방향 어디든 갈 수 있으니 이에 대한 처리가 필요하다.
    # 1. 퀸을 놓으려고 할 때 기존에 놓아진 모든 퀸들과의 상호 확인을 한다.
    # 2. 퀸을 생성 하는 시점에서, 판에 놓을 수 있는 여부를 업데이트 한다.

queenCnt = int(input())
# 퀸의 갯수는 실제 범위와 동일하게 한다.

array = [[0 for col in range(16)] for row in range(16)]
totalResult = 0
# 0은 놓아진 게 없음, 1은 퀸이 설치되었음, 2는 퀸을 설치 할 수 없음으로 하자.

def queenInstaller(array, _x, _y):

    if array[_x][_y] != 0:
        # 배치가 가능한지를 기본적으로 확인해야한다.
        #print("install failed! tried axis is : " + str(_x) + " , " + str(_y))
        return
    # 배치가 가능하다면 배치를 한다.
    array[_x][_y] = 1
    #print("installed in " + str(_x) + " " + str(_y))

    if(_x + 1 == queenCnt):
        # 정한 갯수만큼 설치 했다면 거기서 중단 할 수 있다.

        #print("FULL INSTALL COMPLETED!")
        #for a in range(0, 10):
            #for b in range(0, 10):
                #print(str(array[a][b]), end="")
            #print("")
        global totalResult
        totalResult += 1
        return

    # 배치 금지 구역을 새롭게 설정해야한다.
    for i in range(queenCnt):
        if(_x + i >= queenCnt) or (_y + i >= queenCnt):
            c = 0
        else:
            if(array[_x + i][_y + i] == 0):
                array[_x + i][_y + i] = 2
            #print("blocked " + str(_x + i) + " " + str(_y + i))

            # 남동
        if(_x - i <= 0) or (_y - i <= 0):
            c = 0
        else:
            if(array[_x - i][_y - i] == 0):
                array[_x - i][_y - i] = 2
            #print("blocked " + str(_x - i ) + " " + str(_y - i))

            # 북서
        # 북서쪽 남동쪽 가로지르는 대각선 처리

        tmpXplus = _x + i
        tmpYplus = _y + i
        tmpXminus = _x - i
        tmpYminus = _y - i

        if(tmpYplus >= queenCnt or tmpXminus < 0):
            c = 0
        else:
            if(array[tmpXminus][tmpYplus] == 0):
                array[tmpXminus][tmpYplus] = 2
                #print("blocked " + str(tmpXminus) + " " + str(tmpYplus))

        if(tmpXplus >= queenCnt or tmpYminus < 0):
            c = 0
        else:
            if(array[tmpXplus][tmpYminus] == 0):
                array[tmpXplus][tmpYminus] = 2
                #print("blocked " + str(tmpXplus) + " " + str(tmpYminus))


            # 북동쪽과 남서쪽 가로지르는 대각선 처리

        if(array[_x][i] == 0):
           array[_x][i] = 2

        if(array[i][_y] == 0):
            array[i][_y] = 2


    for j in range(0, queenCnt):
        local_array = [row[:] for row in array]
        queenInstaller(local_array, _x + 1, j)
        # _x 기반으로 움직이게끔 한다. 즉 다음 작동은 
        # 기존 _x 다음줄로 항상 진행되고, _y 가 순차적으로 움직이며 배치를 시도한다.
        # 그럼 결과적으로 _x가 놓는데 성공한 갯수가 된다. 결국 같은 열에선 이뤄 질 수 없으니까
        # 다만 이 설치가 성공한 시점에서 갯수를 세어보고 맞다면 거기서 해당 과정을 중단 할 수 있다.


for i in range(0, queenCnt):
   # print("starting install from " + str(0) + " " + str(i))
    local_array = [row[:] for row in array]
    queenInstaller(local_array, 0, i)

print(totalResult)

다 좋다. 근데 시간을 더 아껴야 했다.
그래서 1차원 배열로 전환하면 시간 상의 이득이 있을 것이라 생각했고 idx와 contents를 하나하나 헷갈려해 나가면서 결국 전환은 했다.

# 퀸은 8방향 어디든 갈 수 있으니 이에 대한 처리가 필요하다.
    # 1. 퀸을 놓으려고 할 때 기존에 놓아진 모든 퀸들과의 상호 확인을 한다.
    # 2. 퀸을 생성 하는 시점에서, 판에 놓을 수 있는 여부를 업데이트 한다.

queenCnt = int(input())
# 퀸의 갯수는 실제 범위와 동일하게 한다.

newArray = [-1] * 16
totalResult = 0

def queenInstaller(arr, idx, installedLoc):
        # 설치 한 적 없는 곳을 보자. 배치가 가능한지를 확인해야한다.
    for i in range(idx):
        if installedLoc == arr[i]:
            return
            # 기존의 설치한거랑 열이 같은지 확인한다.

        if abs(i + arr[i]) == abs(idx + installedLoc):
            return
            # 기존 설치 된 아이템 중 10시 - 5시 방향 대각선을 확인해야한다.

        if (i - arr[i]) == (idx - installedLoc):
            return
            # 기존 설치 된 아이템 중 2시- 8시 방향 대각선을 확인해야한다.

    arr[idx] = installedLoc

    if(idx + 1 == queenCnt):
        global totalResult
        totalResult += 1
        return


    for j in range(0, queenCnt):
        queenInstaller(arr, idx + 1, j)
        # _x 기반으로 움직이게끔 한다. 즉 다음 작동은 
        # 기존 _x 다음줄로 항상 진행되고, _y 가 순차적으로 움직이며 배치를 시도한다.
        # 그럼 결과적으로 _x가 놓는데 성공한 갯수가 된다. 결국 같은 열에선 이뤄 질 수 없으니까
        # 다만 이 설치가 성공한 시점에서 갯수를 세어보고 맞다면 거기서 해당 과정을 중단 할 수 있다.
    arr[idx] = -1

for i in range(0, queenCnt):
   # print("starting install from " + str(0) + " " + str(i))
    queenInstaller(newArray, 0, i)

print(totalResult)

아마 여기서 많이 기운이 나갔다. 그래서 책을 결국에 펴보니 배치에 대한 공격 범위 처리가 좀 더 요약된 상태로 처리 되는 내용이 있었다.
8 방향에 대해 확인한다는 것 중 안해도 되는 내용이 있다. 

  • 행과 열은 만드는 의도에 따라 다르지만 한 쪽을 기준으로 잡고 진행하면 나머지 한 쪽만 보면된다. 심지어 숫자 기존에 등록된 행(또는 열)의 번호만 갖고 있어도 이게 놓을 수 있는 것인지 아닌지를 알 수 있다.
  • 북동쪽과 남서쪽은 일반 좌표계에서 생각하면 y = x + c 인 단순 대각선 구도이다. 이전의 구현은 배치된 내용 기준으로 위 아래를 각기 구분해서 시도했지만 주어진 y x c를 이러쿵 저러쿵 하면 flag로 뒷 마무리를 할 수 있다. 정확하게는 num + i 이다.
  • 북서쪽과 남동쪽은 다소 고민이 필요하다. 여기는 x와 y가 반비례한다. 그렇다는건 음수가 될 수 있는 여지도 생각해야한다. 그래서 일정한 값으로 queen의 갯수를 더 해주게 된다. queen의 갯수는 필드의 넓이이기도 해서 이 부분에 대해서 충분한 수치 교정을 도와 줄 수있다. 따라서 num - 1 + queenCnt - 1을 해주는 것이다. (queenCnt - 1)은 갯수를 idx화 하는 과정에서 발생하는 오차 교정이다.

결과적으론 이걸로 통과하기로 했다.

queenCnt = int(input())
# 퀸의 갯수는 실제 범위와 동일하게 한다.

pos = [0] * 15
flag_line = [False] * 50
flag_5_10 = [False] * 50
flag_2_8 = [False] * 50

totalResult = 0


def installQueen(num):
    for i in range(queenCnt):
        if(not flag_line[i] and not flag_2_8[i + num] and not flag_5_10[num - i + queenCnt - 1]):
            pos[num] = i
            if(num == queenCnt - 1):
                global totalResult
                totalResult += 1
            else:
                flag_line[i] = flag_2_8[i + num] = flag_5_10[num - i + queenCnt - 1] = True
                installQueen(num + 1)
                flag_line[i] = flag_2_8[i + num] = flag_5_10[num - i + queenCnt - 1] = False


installQueen(0)
print(totalResult)

'문제풀이' 카테고리의 다른 글

[PY] : 2630 색종이 만들기  (0) 2025.03.22
[PY] 2805 : 나무 자르기  (0) 2025.03.21
[PY] 1920 : 수 찾기  (0) 2025.03.21
[PY] 1914 : 하노이 탑  (0) 2025.03.20
[PY] 9020 : 골드바흐의 추측  (0) 2025.03.20