728x90
시간 제한 10초, 메모리 제한 128MB
# 조건
- 서양 장기인 체스에는 대각선 방향으로 움직일 수 있는 비숍(bishop)이 있다.
- < 그림 1 >과 같은 정사각형 체스판 위에 B라고 표시된 곳에 비숍이 있을 때 비숍은 대각선 방향으로 움직여 O로 표시된 칸에 있는 다른 말을 잡을 수 있다.
![[Algorithm/baekjoon_python/assets/Pasted image 20230430173854.png]]
- 그런데 체스판 위에는 비숍이 놓일 수 없는 곳이 있다.
- < 그림 2 >에서 체스판에 색칠된 부분은 비숍이 놓일 수 없다고 하자. 이와 같은 체스판에 서로가 서로를 잡을 수 없도록 하면서 비숍을 놓는다면 < 그림 3 >과 같이 최대 7개의 비숍을 놓을 수 있다.
- 색칠된 부분에는 비숍이 놓일 수 없지만 지나갈 수는 있다.
![[Algorithm/baekjoon_python/assets/Pasted image 20230430173914.png]]
- 정사각형 체스판의 한 변에 놓인 칸의 개수를 체스판의 크기라고 한다.
- 체스판의 크기와 체스판 각 칸에 비숍을 놓을 수 있는지 없는지에 대한 정보가 주어질 때, 서로가 서로를 잡을 수 없는 위치에 놓을 수 있는 비숍의 최대 개수를 구하는 프로그램을 작성하시오.
입력
- 첫째 줄에 체스판의 크기가 주어진다.
- 체스판의 크기는 10이하의 자연수이다.
- 둘째 줄부터 아래의 예와 같이 체스판의 각 칸에 비숍을 놓을 수 있는지 없는지에 대한 정보가 체스판 한 줄 단위로 한 줄씩 주어진다.
- 비숍을 놓을 수 있는 곳에는 1, 비숍을 놓을 수 없는 곳에는 0이 빈칸을 사이에 두고 주어진다.
출력
- 첫째 줄에 주어진 체스판 위에 놓을 수 있는 비숍의 최대 개수를 출력한다.
# 접근 방법
- 모든 경우를 백트래킹으로 돌리게 되면, O(2^100)의 시간복잡도가 발생한다.
- 따라서, 우상향 대각선과 우하향 좌선으로 생각해준다.
- 우상향 좌선의 경우 i+j의 값이 일정하고
- 우하향 좌선의 경우 i-j의 값이 일정하다.
- 해당 대각선을 위에서부터 0,1,2... 번호를 매겨준다.
- 우상향 대각선을 기준으로 해당 번호를 인덱스 번호로 사용하여
- 각 대각선마다 비숍을 넣을 수 있는 칸을 기록해준다.
- 사선의 번호는 0 ~ 2 * N - 1 까지 나올 수 있다.
- ex) 0번 대각선 [0,0], 1번 대각선 [0,1] => [[(0,0)], [(0,1)] ... ]
- 0번 인덱스부터 1번, 2번 .. 2 * N -1 개를 가지치기 해주며 백트래킹을 실행해준다.
- dfs를 돌리며 인자는 선택된 대각선 번호 k과, 현재 놓여진 체스말 cnt를 넘겨준다.
- 백트래킹을 위하여 최상단에 가지치기를 해준다.
- 남은 자리를 다 놓아도 ans보다 작은 경우 return
- 종료 조건은 k == 2 * L - 1 인 경우 cnt를 return 해준다.
- 이 경우, 같은 우상향 대각선에 위치한 비숍은 하나씩 돌므로 확인 해 줄 필요없다.
- 따라서 ci - cj 의 칸 => 우하향 대각선만 체크 해주면 된다.
- visited 배열을 2 * L - 1 크기로 만들어 준 후, 해당 우하향 대각선 위치 (ci-cj) 에 비숍이 놓였는지 체크해주면 된다.
- 안 놓고 가는 경우도 체크해주면 되는데 cnt를 그대로 넘겨주는 차이만 있다.
n-queen과 비슷하였지만, 시간을 줄이는데 있어서 조금 힘들었다.
또한, 위 코드에서 서로 영향을 줄 수 없는 칸을 나누거나,
이분 매칭을 이용하여 풀 수도 있는데 아래 유튜브와 블로그를 참고하면 이해하는데 도움이 될 것 같다!!
백트래킹 이용
import sys
sys.stdin = open('input.txt')
def dfs(k, cnt):
global ans
# 가지치기
# 남은 위치를 다 놓아도 작은 경우
if ans >= (cnt+L-k):
return
if k == L:
ans = max(ans, cnt)
return
# 현재 대각선번호에서 가능한 위치 하나씩 놓고 다음 대각선으로 가기
for ci, cj in lst[k]:
if visited[ci-cj] == 0:
visited[ci-cj] = 1
dfs(k+1, cnt+1)
visited[ci-cj] = 0
# 이번 대각선에서 안놓고 다음으로 넘어가기
dfs(k+1, cnt)
n = int(input())
arr = [[*map(int, input().split())] for _ in range(n)]
lst = [[] for _ in range(2*n-1)]
# arr[i][j] == 1 인 경우 i+j 위치에 appendfor i in range(n):
for j in range(n):
if arr[i][j] == 1:
lst[i+j].append((i, j))
L = 2 * n - 1
visited = [0] * L
ans = 0
dfs(0, 0)
print(ans)
위 코드 다듬기
- 위와 같이 흰 칸과 검은 칸으로 나눠서 생각주면 된다.
- 이 때, 서로 다른 색의 칸들은 상호독립적이므로 위의 코드에서 조금만 고쳐주면 된다.
- 검은 칸의 경우 0부터 2씩 증가
- 흰 칸의 경우 1부터 2씩 증가하며
- 가지치기 남은 칸의 경우 // 2 를 통하여 줄여주면 된다.
import sys
sys.stdin = open('input.txt')
def dfs(k, cnt):
global ans
# 가지치기
# 남은 위치를 다 놓아도 작은 경우
# 2개씩 건너뛰므로 // 2 if ans >= (cnt+(L+1-k)//2):
return
if k >= L:
ans = max(ans, cnt)
return
# 현재 대각선번호에서 가능한 위치 하나씩 놓고 다음 대각선으로 가기
for ci, cj in lst[k]:
if visited[ci-cj] == 0:
visited[ci-cj] = 1
dfs(k+2, cnt+1)
visited[ci-cj] = 0
# 이번 대각선에서 안놓고 다음으로 넘어가기
dfs(k+2, cnt)
n = int(input())
arr = [[*map(int, input().split())] for _ in range(n)]
lst = [[] for _ in range(2*n-1)]
# arr[i][j] == 1 인 경우 i+j 위치에 appendfor i in range(n):
for j in range(n):
if arr[i][j] == 1:
lst[i+j].append((i, j))
L = 2 * n - 1
visited = [0] * L
# 체스판의 흑/백은 비숍이 상호간 이동 불가하다.
# 0 부터 시작해서 2씩 증가 + 1부터 시작해서 2씩 증가
ans = 0
# 0부터 2씩 증가
dfs(0, 0)
t = ans
ans = 0
# 1부터 2씩 증가
dfs(1, 0)
print(ans+t)
- 시간 차이가 어마어마하다...
728x90
'ALGORITHM > 백트래킹' 카테고리의 다른 글
[백준 2661번] 파이썬 - 좋은 수열 (0) | 2023.08.02 |
---|---|
[백준 1189번] 파이썬 - 컴백홈 (0) | 2023.05.17 |
[백준 14888번] 파이썬 - 연산자 끼워넣기 (0) | 2023.04.07 |
[백준 2239번] 파이썬 - 스도쿠 (0) | 2023.01.29 |
[백준 9663번] 파이썬 - N-Queen (1) | 2022.12.15 |