프로그래밍/문제풀이

[Python] 백준 19238 스타트 택시

성수동이민기 2022. 10. 3. 15:53

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

 

19238번: 스타트 택시

첫 줄에 N, M, 그리고 초기 연료의 양이 주어진다. (2 ≤ N ≤ 20, 1 ≤ M ≤ N2, 1 ≤ 초기 연료 ≤ 500,000) 연료는 무한히 많이 담을 수 있기 때문에, 초기 연료의 양을 넘어서 충전될 수도 있다. 다

www.acmicpc.net

 

# 19238 스타트 택시

# 택시의 움직임을 나타냄, 움직인 거리가 return
def move(current_cus_r, current_cus_c, end_pos_r, end_pos_c):
    temp_visited_2 = [vi[:] for vi in visited]
    temp_visited_2[current_cus_r][current_cus_c] = 1
    queue =[[current_cus_r, current_cus_c, 0]] # r, c, distance 담김
    while queue:
        qr, qc, qdis = queue.pop(0)
        for i in range(4):
            cr = qr + dr[i]
            cc = qc + dc[i]
            # 범위 안에 있으면서 방문한 적이 없다면
            if 0 < cr <= N and 0 < cc <= N and temp_visited_2[cr][cc] == 0:
                temp_visited_2[cr][cc] = 1
                # 목적지에 도달했다면 거리 계산해서 return
                if cr == end_pos_r and cc == end_pos_c:
                    return qdis + 1
                queue.append([cr, cc, qdis + 1])
    # 목적지에 도달하지 못하고 queue 빠져나왔다면 -1 return
    return -1


# 상 좌 하 우
dr = [-1, 0, 1, 0]
dc = [0, -1, 0, 1]

N, M, fuel = map(int, input().split())
grounds = [[1] * (N + 1)] + [[1] + list(map(int, input().split())) for _ in range(N)]
visited = [[0] * (N + 1) for _ in range(N + 1)]
for r in range(N + 1):
    for c in range(N + 1):
        if grounds[r][c] == 1:
            visited[r][c] = 1

start_r, start_c = map(int, input().split())
customer = list()
for _ in range(M):
    cus_r, cus_c, end_r, end_c = map(int, input().split())
    customer.append([cus_r, cus_c, end_r, end_c])

while customer:
    # 택시 위치 정하기
    queue = [[start_r, start_c]]
    # 임시 방문 목록, 임시 grounds 만들기
    temp_visited = [vi[:] for vi in visited]
    temp_visited[start_r][start_c] = 1 # 현재 택시 위치 방문 표시
    temp_grounds = [gr[:] for gr in grounds]
    while queue:
        qr, qc = queue.pop(0)
        for i in range(4):
            cr = qr + dr[i]
            cc = qc + dc[i]
            # 범위 안에 있으면서 방문한 적이 없는 경우
            if 0 < cr <= N and 0 < cc <= N and temp_visited[cr][cc] == 0:
                temp_visited[cr][cc] = 1
                temp_grounds[cr][cc] = temp_grounds[qr][qc] + 1
                queue.append([cr, cc])

    # 승객 중 최소 거리 구하기
    cus_idx = -1
    cus_minimum = 98765
    end_pos_r = start_r
    end_pos_c = start_c

    for idx, (current_r, current_c, current_end_r, current_end_c) in enumerate(customer):
        # 택시 출발지와 손님 출발지가 같을 경우 바로 return
        if current_r == start_r and current_c == start_c:
            # customer 내 idx, customer 최소 이동거리, 손님 시작 지점 r c, 손님 목적지 r c 갱신
            cus_idx = idx
            cus_minimum = temp_grounds[current_r][current_c]
            current_cus_r = current_r
            current_cus_c = current_c
            end_pos_r = current_end_r
            end_pos_c = current_end_c
            break
        # grounds가 유효한 값이면서 현재 최소거리보다 더 작은 거리인 경우
        if temp_grounds[current_r][current_c] > 0 and cus_minimum > temp_grounds[current_r][current_c]:
            cus_idx = idx
            cus_minimum = temp_grounds[current_r][current_c]
            current_cus_r = current_r
            current_cus_c = current_c
            end_pos_r = current_end_r
            end_pos_c = current_end_c
        # grounds가 유효한 값이면서 현재 최소거리와 같은 경우
        elif temp_grounds[current_r][current_c] > 0 and cus_minimum == temp_grounds[current_r][current_c]:
            # 행이 더 작은 경우 갱신
            if current_cus_r > current_r:
                cus_idx = idx
                cus_minimum = temp_grounds[current_r][current_c]
                current_cus_r = current_r
                current_cus_c = current_c
                end_pos_r = current_end_r
                end_pos_c = current_end_c
            # 행은 같지만 열이 더 작은 경우 갱신
            elif current_cus_r == current_r and current_cus_c > current_c:
                cus_idx = idx
                cus_minimum = temp_grounds[current_r][current_c]
                current_cus_r = current_r
                current_cus_c = current_c
                end_pos_r = current_end_r
                end_pos_c = current_end_c

    # cus_idx가 없으면 break
    if cus_idx == -1:
        fuel = -1
        break
    # 현재 보고 있는 customer의 정보 방출
    customer.pop(cus_idx)

    # 연료 빼주기
    fuel -= cus_minimum
    if fuel < 0:
        fuel = -1
        break

    # 승객 위치로 이동
    move_dis = move(current_cus_r, current_cus_c, end_pos_r, end_pos_c)
    # 사용한 연료 계산
    fuel -= move_dis
    # 연료가 0보다 작거나 move_dis == -1인 경우(유효한 움직임이 아님)
    if fuel < 0 or move_dis == -1:
        fuel = -1
        break
    # 2배 충전
    fuel += (move_dis * 2)

    # 택시 시작 위치 재설정
    start_r = end_pos_r
    start_c = end_pos_c

print(fuel)

"""
0% 틀렸습니다.
cus_idx == -1일 때 예외처리 추가
택시 출발지와 손님 출발지 같을 경우 고려

80% 틀렸습니다.
move 함수의 결과인 move_dis가 move_dis == -1 일 때 예외처리 추가

### 참고) 손님 위치의 행(r), 열(c) 계산시
delta 탐색 순서를 상 좌 하 우 로 했다고 해서 안심할 수 없고, 택시와의 거리가 최솟값인 손님들 일일이 행, 열 보고 판단해야 함.

ㅂ=빈칸
ㅃ=손님
ㅇ=택시

ㅂㅂㅂㅂㅂㅃ
ㅃㅂㅂㅇㅂㅂ
ㅂㅂㅂㅂㅂㅂ

위의 예시에서, 1행 6열 손님을 태워야 하지만
상 좌 하 우 순서로 탐색하면, 2행 1열 손님을 태우는 것으로 인지하게 됨. 
따라서 거리 최솟값이 같은 손님이라면 일일이 대조해봐야 됨.
"""

 

 

 

예전 풀이

 

# 19238 스타트 택시

# 상 좌 우 하
dr = [-1, 0, 0, 1]
dc = [0, -1, 1, 0]

def find_customer(qr, qc):
    visited = [[-1] * (N + 1) for _ in range(N + 1)]
    queue = [[qr, qc]]
    visited[qr][qc] = 0
    my_r, my_c, my_visit = 987654, 987654, 987654
    cur_dis = 987654
    while queue:
        kr, kc = queue.pop(0)
        for cus in customers:
            # 손님이 있으면 갈 때까지 걸린 거리 return
            if kr == cus[0] and kc == cus[1] and cur_dis >= visited[kr][kc]:
                if my_r > kr:
                    my_r = kr
                    my_c = kc
                    my_visit = visited[kr][kc]
                    cur_dis = visited[kr][kc]
                elif my_r == kr and my_c > kc and cur_dis >= visited[kr][kc]:
                    my_r = kr
                    my_c = kc
                    my_visit = visited[kr][kc]
                    cur_dis = visited[kr][kc]

        for i in range(4):
            cr = kr + dr[i]
            cc = kc + dc[i]
            if 1 <= cr <= N and 1 <= cc <= N and visited[cr][cc] == -1 and roads[cr][cc] == 0:
                visited[cr][cc] = visited[kr][kc] + 1
                for cus in customers:
                    # 손님이 있으면 갈 때까지 걸린 거리 return
                    if cr == cus[0] and cc == cus[1] and cur_dis >= visited[cr][cc]:
                        if my_r > cr:
                            my_r = cr
                            my_c = cc
                            my_visit = visited[cr][cc]
                            cur_dis = visited[cr][cc]
                        elif my_r == cr and my_c > cc and cur_dis >= visited[cr][cc]:
                            my_r = cr
                            my_c = cc
                            my_visit = visited[cr][cc]
                            cur_dis = visited[cr][cc]
                queue.append([cr, cc])

    return my_r, my_c, my_visit


def go_taxi(qr, qc, start_r, start_c):
    visited = [[-1] * (N + 1) for _ in range(N + 1)]
    queue = [[qr, qc]]
    visited[qr][qc] = 0
    while queue:
        kr, kc = queue.pop(0)
        for idx, cus in enumerate(customers):
            if cus[2] == kr and cus[3] == kc and cus[0] == start_r and cus[1] == start_c:
                customers[idx][2] = -1
                customers[idx][3] = -1
                return kr, kc, visited[kr][kc]

        for i in range(4):
            cr = kr + dr[i]
            cc = kc + dc[i]
            if 1 <= cr <= N and 1 <= cc <= N and visited[cr][cc] == -1 and roads[cr][cc] == 0:
                visited[cr][cc] = visited[kr][kc] + 1
                # 손님이 있으면 갈 때까지 걸린 거리 return
                for idx, cus in enumerate(customers):
                    if cus[2] == cr and cus[3] == cc and cus[0] == start_r and cus[1] == start_c:
                        customers[idx][0] = -1
                        customers[idx][1] = -1
                        customers[idx][2] = -1
                        customers[idx][3] = -1
                        return cr, cc, visited[cr][cc]

                queue.append([cr, cc])
    return -1, -1, 987654


N, M, fuel = map(int, input().split())
roads = [[0] * (N + 1)] + [[0] + list(map(int, input().split())) for _ in range(N)]
cur_location = list(map(int, input().split()))
customers = [list(map(int, input().split())) for _ in range(M)]
result = 0

while M > 0:
    # 최단거리 승객 찾기
    cr, cc, num = find_customer(cur_location[0], cur_location[1])
    cur_location = [cr, cc]
    # 손님에게 이동
    fuel -= num
    if fuel < 0 or cur_location[0] == -1:
        fuel = -1
        break
    # # 손님의 목적지로 이동
    cr, cc, num = go_taxi(cur_location[0], cur_location[1], cr, cc)
    cur_location = [cr, cc]
    fuel -= num
    if fuel < 0 or cur_location[0] == -1:
        fuel = -1
        break
    fuel += (num * 2)
    M -= 1

print(fuel)