https://www.acmicpc.net/problem/17779
문제의 길이가 매우 길므로
게리멘더링에 대해서만 집고 가겠습니다.
저런 식으로 경계를 만드는 건데
규칙은 이렇습니다.
저는 이런 순서대로 코드를 구현했습니다.
1. 4가지 경계선 먼저 그리기
2. 경계선 안에 5 선거구로 채워넣기
3. 나머지 구역에는 차례대로 1 2 3 4 넣기
처음에는
1 2 3 4 선거구를 넣은 다음에
나머지 구역은 선거구 5로 넣는걸로 구현했다가
원래 5로 넣어져야할 부분이
다른 선거구로 채워지는 실수를 범해서
위의 순서대로 코드를 구현했습니다.
어떻게 경계선 안을 채워넣을까 했을 때
BFS에서의 길찾기를 응용하기로 했습니다.
즉, 경계선 안에 있는 좌표를 queue로 받고
5가 아닌 곳에 5를 채우는 식이었죠.
처음에는 지정한 좌표 바로 아래를
(x값-1한 좌표)
큐에 넣고 BFS를 시켜줬습니다.
그러더니 코드가 삐끗했습니다.
바로 아래에
x=2, y=4
d1=2, d2=1
인 케이스가 문제였죠.
이 케이스에서 지정된 좌표 바로 아래를 고르면
하나는 채워넣지 못했습니다.
그래서 다소 복잡하긴 하지만
넣는 케이스를 4가지 추가했습니다.
경계점 1 스타트의 아랫값
경계점 2 스타트의 오른칸
경계점 3 스타트의 윗칸
경계점 4 스타트의 왼칸.
쉽게 생각해서
꼭짓점에서 한 칸 들어간 걸
각각의 좌표로 받았습니다.
물론 중복이 되는 경우에는
당연히 제외를 시키도록 만들었고요
이렇게 만드니 코드가 정상적으로 가동하여
정답을 맞출 수 있었습니다.
# 17779 게리멘더링2
from collections import deque
N = int(input())
A = [list(map(int, input().split())) for _ in range(N)]
dr = [1, 1, 1, 1]
dc = [-1, 1, 1, -1] # 1 2 3 4 경계선 규칙에 따라서 정함.
moving_r = [-1, 0, 1, 0]
moving_c = [0, -1, 0, 1]
answer = 1e9
# 모든 점들 각각을 기준점으로 잡고 경계선의 길에 따른 모든 경우를 구해보자
for r in range(N):
for c in range(N):
for d_1 in range(1, N // 2):
for d_2 in range(1, N // 2):
# 경계선의 범위 설정
if r + d_1 + d_2 >= N:
break
if c - d_1 < 0:
break
if c + d_2 >= N:
break
one = 0
two = 0
three = 0
four = 0
five = 0
visited = [[0] * N for _ in range(N)]
visited[r][c] = 5
five += A[r][c]
# print(r,c,d_1,d_2)
# 1번 경계선
time = 0
one_r = r
one_c = c
if not visited[one_r][one_c]:
visited[one_r][one_c] = 5
five += visited[one_r][one_c]
while time < d_1:
one_r += dr[0]
one_c += dc[0]
if (one_r < 0 or one_r >= N) or (one_c < 0 or one_c >= N):
break
if not visited[one_r][one_c]:
visited[one_r][one_c] = 5
five += A[one_r][one_c]
time += 1
# 2번 경계선
time = 0
two_r = r
two_c = c
if not visited[two_r][two_c]:
visited[two_r][two_c] = 5
five += visited[two_r][two_c]
while time < d_2:
two_r += dr[1]
two_c += dc[1]
if (two_r < 0 or two_r >= N) or (two_c < 0 or two_c >= N):
break
if not visited[two_r][two_c]:
visited[two_r][two_c] = 5
five += A[two_r][two_c]
time += 1
# 3번 경계선
time = 0
three_r = r + d_1
three_c = c - d_1
if not visited[three_r][three_c]:
visited[three_r][three_c] = 5
five += visited[three_r][three_c]
while time < d_2:
three_r += dr[2]
three_c += dc[2]
if (three_r < 0 or three_r >= N) or (three_c < 0 or three_c >= N):
break
if not visited[three_r][three_c]:
visited[three_r][three_c] = 5
five += A[three_r][three_c]
time += 1
# 4번 경계선
time = 0
four_r = r + d_2
four_c = c + d_2
if not visited[four_r][four_c]:
visited[four_r][four_c] = 5
five += visited[four_r][four_c]
while time < d_1:
four_r += dr[3]
four_c += dc[3]
if (four_r < 0 or four_r >= N) or (four_c < 0 or four_c >= N):
break
if not visited[four_r][four_c]:
visited[four_r][four_c] = 5
five += A[four_r][four_c]
time += 1
# 5번 선거구를 먼저 지정
# 1번째 스타트의 아랫칸, 2번째 스타트의 오른칸,
# 3번째 스타트의 윗칸, 4번째 스타트의 왼칸
# 이렇게를 불러온다
q = deque()
m_r = r + 1
m_c = c
if not visited[m_r][m_c]:
visited[m_r][m_c] = 5
five += A[m_r][m_c]
q.append((m_r, m_c))
m_r = r + d_2
m_c = c + d_2 - 1
if not visited[m_r][m_c]:
visited[m_r][m_c] = 5
five += A[m_r][m_c]
q.append((m_r, m_c))
m_r = r + d_1
m_c = c - d_1 + 1
if not visited[m_r][m_c]:
visited[m_r][m_c] = 5
five += A[m_r][m_c]
q.append((m_r, m_c))
m_r = r + d_1 + d_2 - 1
m_c = c - d_1 + d_2
if not visited[m_r][m_c]:
visited[m_r][m_c] = 5
five += A[m_r][m_c]
q.append((m_r, m_c))
# 칸이 위치 탐색을 상하좌우로 하면서 채워나감
while q:
n_r, n_c = q.popleft()
for i in range(4):
go_r = n_r + moving_r[i]
go_c = n_c + moving_c[i]
if (go_r < 0 or go_r >= N) or (go_c < 0 or go_c >= N):
break
if not visited[go_r][go_c]:
visited[go_r][go_c] = 5
five += A[go_r][go_c]
q.append((go_r, go_c))
# 1 2 3 4번 선거구를 지정
# 1번 선거구
for x in range(0, r + d_1):
for y in range(0, c + 1):
if not visited[x][y]:
one += A[x][y]
visited[x][y] = 1
# 2번 선거구
for x in range(0, r + d_2 + 1):
for y in range(c + 1, N):
if not visited[x][y]:
two += A[x][y]
visited[x][y] = 2
# 3번 선거구
for x in range(r + d_1, N):
for y in range(0, c - d_1 + d_2):
if not visited[x][y]:
three += A[x][y]
visited[x][y] = 3
# 4번 선거구
for x in range(r + d_2 + 1, N):
for y in range(c - d_1 + d_2, N):
if not visited[x][y]:
four += A[x][y]
visited[x][y] = 4
# print(one,two,three,four,five)
min_po = min(one, two, three, four, five)
max_po = max(one, two, three, four, five)
now_po = max_po - min_po
# print(now_po)
answer = min(answer, now_po)
print(answer)
이 문제는 부피가 크다보니
디버깅하는 게 조금 빡셌습니다.
저는 디버깅을 할 떄
r c d1 d2를 다 받고
어라?이거 왜 값이 이상하지?
하면 해당 r c 값에 대해서
if r==.... and c==....
이런 식으로 해서
제가 원하는 값만을 보도록 출력했습니다.
저는 선거구 5를 만들 때
경계선을 만들고
채워넣는다는 식으로 접근했는데
구글링해본 결과
아예 이동할 때 채우신 분들도 많았습니다.
저처럼 바로 대각선으로 이동한 게 아닌
x y축 각각 이동하면서
차례대로 넣는 방식이었습니다.
코드를 실제로 구현한 결과
그 방식이 더 간단하긴 합니다.
이 방식은 노가다를 좀 해야하는 단점이...ㅎ
+++++
경계선을 만들고 채워넣는 방식도 가져와봤습니다.
https://pacific-ocean.tistory.com/382
해당 솔루션을 저의 식대로 바꾸었습니다.
다른 점이라면, 이 분은 간단하게
행렬을 1부터 N까지 냅두었다면
저는 0부터 N-1까지 냅두었습니다.
여기서 주의할 점은
5를 채워넣는 부분입니다.
해당 코드에서는
맨 위 경계선점 바로 아래행부터
맨 아래 경계선점 바로 윗행까지를
5로 채워넣는 개념으로 접근했습니다.
만약에 경계선 처음부터 마지막까지하면
경계선 이후에 5555555가 계속 더해져서
범위가 이상하게 되는 불상사가 발생합니다.
(범위는 프로그래밍의 생명이다)
# 백준 177779번 게리맨더링2
N = int(input())
A = [list(map(int, input().split())) for _ in range(N)]
dr = [0, 0, 1, -1]
dc = [1, -1, 0, 0]
max_result = 100000000
for x in range(N):
for y in range(N):
for d1 in range(1, N):
for d2 in range(1, N):
if 0 <= x + d1 + d2 <= N - 1 and 0 <= y - d1 < y < y + d2 <= N - 1:
temp = [[0] * N for _ in range(N)]
cal = [0 for i in range(5)]
# 5경계선
for i in range(d1 + 1):
temp[x + i][y - i] = 5
temp[x + d2 + i][y + d2 - i] = 5
for i in range(d2 + 1):
temp[x + i][y + i] = 5
temp[x + d1 + i][y - d1 + i] = 5
# 5의 구역 넣기
# 경계선 아랫행부터 채운다
# 경계선부터 체우면 뒤를 555555로 채우는 불상사 발생.
for i in range(x + 1, x + d1 + d2):
isTrue = False
for j in range(N):
if temp[i][j] == 5:
isTrue = not isTrue
if isTrue: temp[i][j] = 5
# 모든 구역 인구수
for r in range(N):
for c in range(N):
if r < x + d1 and c <= y and temp[r][c] == 0:
cal[0] += A[r][c]
elif r <= x + d2 and y < c and temp[r][c] == 0:
cal[1] += A[r][c]
elif x + d1 <= r and c < y - d1 + d2 and temp[r][c] == 0:
cal[2] += A[r][c]
elif x + d2 < r and y - d1 + d2 <= c and temp[r][c] == 0:
cal[3] += A[r][c]
elif temp[r][c] == 5:
cal[4] += A[r][c]
now_result = max(cal) - min(cal)
max_result = min(max_result, now_result)
print(max_result)
시간측면에서는 위의 코드가 더 짧긴한데
가독성 측면에서는 아래코드가 더 좋았습니다.
최대한 문제대로 코드를 구현하는 것이
프로그래머 입장에서 더 생각이 정리가 잘 될 듯 합니다.
(내용추가-2022.10.13)
from collections import deque
dx=[0,0,1,-1]
dy=[1,-1,0,0]
N=int(input())
room=[list(map(int,input().split())) for _ in range(N)]
answer=1e9
def inside(num):
if num<0 or num>=N:
return False
return True
def geree(x,y,d1,d2):
global answer
board=[[0]*N for _ in range(N)]
#print('start',x,y,d1,d2)
hap=[0,0,0,0,0,0]
#구역을 나누고 각 구역의 인구수 더하기
#5구역 나눌 때, 안의 넣을거 구해주기
tmp=set()
ha=set()
#print('one')
for d in range(0,d1+1):
nx=x+d
ny=y-d
board[nx][ny]=5
ha.add((nx,ny))
if d>=1:
tmp.add((nx,ny+1))
#print(nx,ny)
#print('two')
for d in range(0,d2+1):
nx=x+d
ny=y+d
board[nx][ny]=5
ha.add((nx, ny))
if d>=1:
tmp.add((nx,ny-1))
#print(nx,ny)
#print('three')
for d in range(0,d2+1):
nx=x+d1+d
ny=y-d1+d
board[nx][ny]=5
ha.add((nx, ny))
if d<d2:
tmp.add((nx,ny+1))
#print(nx,ny)
#print('four')
for d in range(0,d1+1):
nx=x+d2+d
ny=y+d2-d
board[nx][ny]=5
ha.add((nx, ny))
if d<d1:
tmp.add((nx,ny-1))
#print(nx,ny)
#5구역 인구수
q=deque()
for tx,ty in tmp:
q.append((tx,ty))
while q:
px,py=q.popleft()
board[px][py]=5
ha.add((px, py))
for d in range(4):
nx=px+dx[d]
ny=py+dy[d]
if board[nx][ny]==5:
continue
q.append((nx,ny))
board[nx][ny]=5
for tx,ty in ha:
hap[5]+=room[tx][ty]
#나머지 구역들
#1구역
for nx in range(0,x+d1):
for ny in range(0,y+1):
if board[nx][ny]==5:
break
board[nx][ny]=1
hap[1]+=room[nx][ny]
#2구역
for nx in range(0,x+d2+1):
for ny in range(N-1,y,-1):
if board[nx][ny]==5:
break
board[nx][ny]=2
hap[2]+=room[nx][ny]
#3구역
for nx in range(x+d1,N):
for ny in range(0,y-d1+d2):
if board[nx][ny]==5:
break
board[nx][ny]=3
hap[3]+=room[nx][ny]
#4구역
for nx in range(x+d2+1,N):
for ny in range(N-1,y-d1+d2-1,-1):
if board[nx][ny]==5:
break
board[nx][ny]=4
hap[4]+=room[nx][ny]
# if x==3 and y==2 and d1==1 and d2==1:
# for k in range(N):
# print(board[k])
ma_p=max(hap[1:])
mi_p=min(hap[1:])
n_cha=ma_p-mi_p
answer=min(answer,n_cha)
for x in range(N):
#y좌표가 1~N-2일때를 보셔도 무방함. 킹냐. 0이나 N-1일때는 d1이든 d2든 1이 되면 범위를 벗어나서 어처피 못한다
for y in range(1,N-1):
for d1 in range(1,N):
for d2 in range(1,N):
if not inside(x+d1) or not inside(x+d2) or not inside(y-d1) or not inside(y+d2) or not inside(x+d1+d2):
break
geree(x,y,d1,d2)
print(answer)
이번에는 시간을 더 줄여보기 위해서
게리멘더링 좌표 구하는 것부터 손을 봤습니다.
우선, 생각해보니 굳이 모든 좌표를 구할 필요없고
y좌표는 0과 N-1를 건너뛰어도 무방합니다.
왜냐하면 행 양끝단에서는 d1,d2가 1이 되면 무조건 범위를 벗어나기 때문이죠.
(추가적으로 x좌표도 0~N-1까지 탐방해도 무방하긴 하던데
백준에서 python으로 제출했을 땐 N-1까지 탐색했을때가 시간이 약간 더 걸린다고 뜨긴합니다)
게리멘더링 좌표를 구하면, 경계선을 먼저 만들어줍니다.
이때, 경계선 안을 queue안에 넣는 작업을 합니다.
저는 집합을 만들어주어서
중복이 되는 케이스도 자연적으로 해결했습니다.
여기서 주의할 점은!!!
1,2 케이스는 d>=1이상부터
3,4 케이스는 d가 끝점 전까지
경계를 넣어주는 점.
1,2,의 처음 시작점은 x,y인데
저 조건을 안 넣어주면
시작점 왼쪽,오른쪽 좌표도 넣어줘서 이상하게 되더라고요.
그리고 3,4는 마지막이 꼭지점인데
마지막까지 넣어주면 그 꼭지점 좌우도 받게 됩니다.
경계선 안 쪽 채워주는 부분에서
계속 범위초과 오류가 떠서 살펴보니 이 점이 문제였습니다.
그 이후에는 1,2,3,4도 범위대로 구한 후에 값을 더해주면 됩니다.
(디버깅이 조금 어렵게 느껴질 수 있는데요.
저는 예제의 케이스를 직접 출력해보면서 디버깅했습니다.
문제설명에서 x=2,y=4,d1=2,d2=2일 때 범위가 나와서
저도 이거에 맞춰서
'이때의 범위칠해주는 게 어떻게 되는지'출력했습니다.)
여기서 주의할 점은
5의 인구수를 구하는 부분.
저는 중복을 피하기 위해
경계선+경계선 안을 집합으로 전부 받은 뒤에
다 채운 후 하나씩 꺼내서 더해줬습니다.
[프로그래머스Lv.3] N으로 표현 (0) | 2022.03.17 |
---|---|
[백준 21610번] 마법사 상어와 비바라기(파이썬) (0) | 2022.03.11 |
[백준14890번] 경사로(파이썬) (0) | 2022.03.06 |
[백준 14888번] 연산자 끼워넣기 (0) | 2022.03.06 |
[프로그래머스]삼각달팽이(파이썬) (0) | 2022.03.04 |
댓글 영역