런타임 에러(IndexError)
반례를 찾기 전까진 "맞왜틀"을 호소했습니다. IndexError가 일어나는 이유를 몰랐거든요.. 하지만 검증 과정에서 문제가 있었습니다.
아래의 소스코드를 보면 필드를 벗어나는 경우를 검증하지 않습니다. BFS/DFS때 흔히 하는 0<=nx<n 이런 검증 말이죠.
그래서 런타임 에러가 발생했고 이를 조건문 검증 후 제출했지만 틀렸습니다가 떴네요. 아래에서 추가 설명하겠습니다.
else:#그냥 길인 경우
field[ny][nx]=2#그 자리에 머리를 넣어줌
field[ty][tx]=0#꼬리 있던 자리를 없애줌
for tdx,tdy in [[0,1],[1,0],[-1,0],[0,-1]]:#다음 몸통 자리를 찾음
ndx,ndy = tx+tdx,ty+tdy
if field[ndy][ndx]==2:#몸통을 찾았다면
tailq.append([ndx,ndy])#그 몸통이 꼬리가 된다.
break
q.append([nx,ny])
틀렸습니다
지나다니는 길을 0 뱀의 머리를 빨간색 2, 꼬리를 파란색 2라고 가정하겠습니다.
아래 그림을 보시면 이해가 바로 되실까요?
2 | 2 | 2 | |||||||
2 | 2 | 2 | |||||||
2 | 2 | ||||||||
2 | 2 | ||||||||
런타임 에러의 소스코드를 보시게 되면 꼬리는 이어져 있는 몸통을 찾기 위해 상, 하, 좌, 우로 서칭을 하게 됩니다.
자 위의 경우 몸통과 머리가 모두 붙어 있을 때 이어진 몸통을 찾는 게 아니라 엉뚱한 곳을 찾게 되면서 꼬리의 위치가 꼬여버리는 문제가 생깁니다. 그래서 이 부분을 자각하고 리스트와 덱(Deque)을 대공사 했습니다.
문제 설명
'뱀 게임', '지렁이 게임'이라고도 합니다. 한 칸씩 이동하며 사과를 먹는 게임이죠.
사과를 먹으면 몸의 길이가 1씩 증가합니다. 단, 사과를 다 먹는다고 해서 게임이 끝나는 게 아니라 벽에 닿거나 자신의 몸통과 겹쳐 버리면 게임이 끝나는 구조입니다.
소스 코드
Python3
필드에 뱀(2), 사과(1), 길(0)을 따로 저장해 두고 뱀의 좌표(SNAKE)를 저장했습니다. 뱀의 좌표(SNAKE)는 머리와 꼬리만 움직이면 되는 구조이므로 끝 부분의 append, pop에 용이한 Deque을 사용했습니다.
drcControl 함수는 뱀의 방향 전환을 해주고 옳은 방향으로 direction을 수정해 주는 역할입니다.
i의 값이 튀어 indexerror를 만들기 전에 조건문으로 주어진 범위 내에서만 작동할 수 있도록 지정해 두었습니다.
풀고 나니 평소 풀던 DFS/BFS보다 정말 쉬웠던 문제인데, 게임 형태로 문제가 나오니 많이 당황했던 것 같습니다.
##########################################################
import sys
from collections import deque
# direction = [[0,1],[-1,0],[1,0],[0,-1]] #for BFS
input = sys.stdin.readline
def ip():return input().rstrip()
def lip():return list(ip())
def ips():return ip().split()
def ii():return int(input())
def mii():return map(int,ips())
def lmii():return list(mii())
##########################################################
n = ii()#보드의 크기
k = ii()#사과의 개수
field = [[0]*n for _ in range(n)]
for _ in range(k):
a,b = mii()
field[a-1][b-1]=1 #1은 사과
l = ii()#뱀의 방향 변환 횟수
turn = deque()
for _ in range(l):
a,b = ips()
turn.append([int(a),b])
turn = deque(sorted(turn,key=lambda x:x[0]))#시간순 정렬
def drcControl(direc,turnd):#현재 방향과 바꾸는 방향
s = [[1,0],[0,1],[-1,0],[0,-1]]
for i,[a,b] in enumerate(s):
if [a,b]==direc:
if turnd=='L':
if i==0:i=4
return s[i-1]
else:
if i==3:i=-1
return s[i+1]
def solve():
global k
direction = [1,0]#오른쪽이 기본 방향
field[0][0]=2
SNAKE = deque([[0,0]])#뱀 꼬리-몸통-머리 구조를 나타낸다.
move = 0
while SNAKE:
x,y = SNAKE[-1]#머리
if turn and turn[0][0]==move:#방향 전환 먼저
direction = drcControl(direction,turn[0][1])
turn.popleft()
dx,dy = direction
nx,ny = dx+x,dy+y
if 0>nx or nx>=n or 0>ny or ny>=n or field[ny][nx]>1:#엔딩 조건
return move+1
if field[ny][nx]==1:#사과를 만난 경우
field[ny][nx]=2#그 자리에 머리를 넣어줌
SNAKE.append([nx,ny])#우측에 좌표를 추가해서 머리를 갱신해줌
else:#그냥 길인 경우
field[ny][nx]=2#그 자리에 머리를 넣어줌
tx,ty=SNAKE.popleft()#꼬리를 빼준다.
field[ty][tx]=0#꼬리 있던 자리를 없애줌
SNAKE.append([nx,ny])#머리를 넣어준다.
move+=1#시간 증가
print(solve())
Java8 (21.02.02 추가)
81~84줄 주석을 해제하면 뱀이 이동하는 모습을 볼 수 있습니다.
package boj3190;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.StringTokenizer;
public class Main {
static class Pos{
int x,y;
Pos(int x,int y){
this.x = x;
this.y = y;
}
}
static class Move{
int move;
char direction;
public Move(int move, char direction) {
this.move = move;
this.direction = direction;
}
}
static BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
static StringTokenizer st;
static int N,K,L;
static int dirX[] = {1,0,-1,0};//우하좌상
static int dirY[] = {0,1,0,-1};
static StringBuilder sb = new StringBuilder();
public static void main(String[] args) throws IOException{
st = new StringTokenizer(bf.readLine());
N = Integer.parseInt(st.nextToken());//필드수
int[][] field = new int[N][N];
st = new StringTokenizer(bf.readLine());
K = Integer.parseInt(st.nextToken());//사과
for(int i =0;i<K;i++) {
st = new StringTokenizer(bf.readLine());
field[Integer.parseInt(st.nextToken())-1][Integer.parseInt(st.nextToken())-1]=1;//사과는 1
}
Deque<Pos> snake = new ArrayDeque<Main.Pos>();//뱀 관리 꼬리-몸통-머리 순
snake.add(new Pos(0,0));//몸추가
field[0][0]=2;//뱀은 2
Deque<Move> query = new ArrayDeque<>();//정수X, 문자C
st = new StringTokenizer(bf.readLine());
L = Integer.parseInt(st.nextToken());//방향전환 횟수
for(int i =0;i<L;i++) {//쿼리삽입
st = new StringTokenizer(bf.readLine());
query.add(new Move(Integer.parseInt(st.nextToken()),st.nextToken().charAt(0)));
}
int cur_direction = 0;//오른쪽으로 스타트
int answer =0 ;
while(true) {
Move tmp = query.poll();
int move = 99999;//쿼리를 다 수행했더라도 뱀은 계속 움직여야함.
if(tmp != null)
move = tmp.move;
for(int i =answer;i<move;i++) {
//다음 이동되는 좌표
int nx = snake.peekLast().x+dirX[cur_direction];
int ny = snake.peekLast().y+dirY[cur_direction];
if(0<=nx && nx<N && 0<=ny && ny<N && field[ny][nx]!=2) {
if(field[ny][nx]==1) {//사과가있는 경우 몸을 늘린다.
field[ny][nx]=2;
snake.addLast(new Pos(nx,ny));//머리를 사과자리에.
}
else {//단순이동
Pos tmp2 = snake.pollFirst();//꼬리제거
field[tmp2.y][tmp2.x]=0;
snake.addLast(new Pos(nx,ny));//머리로이동
field[ny][nx]=2;
}
answer++;
// System.out.println("=================="+answer);
// for(int z =0;z<field.length;z++) {
// System.out.println(Arrays.toString(field[z]));
// }
}
else {//벽에 또는 몸통에 부딪히면 게임종료
System.out.println(answer+1);
return;
}
}
//움직인 이후 방향전환
if(tmp.direction=='D') {
cur_direction++;
if(cur_direction>3)cur_direction=0;
}
else if(tmp.direction=='L') {
cur_direction--;
if(cur_direction<0)cur_direction=3;
}
}
}
}