ProblemSolving/DP

프로그래머스 파괴되지 않은 건물 (파이썬)

OSNIM 2022. 5. 22. 15:41
반응형

2022 KAKAO BLIND RECRUITMENT 6번 

문제 : https://programmers.co.kr/learn/courses/30/lessons/92344?language=python3 

 

코딩테스트 연습 - 파괴되지 않은 건물

[[5,5,5,5,5],[5,5,5,5,5],[5,5,5,5,5],[5,5,5,5,5]] [[1,0,0,3,4,4],[1,2,0,2,3,2],[2,1,0,3,1,2],[1,0,1,3,3,1]] 10 [[1,2,3],[4,5,6],[7,8,9]] [[1,1,1,2,2,4],[1,0,0,1,1,2],[2,2,0,2,0,100]] 6

programmers.co.kr

문제 설명

[본 문제는 정확성과 효율성 테스트 각각 점수가 있는 문제입니다.]

N x M 크기의 행렬 모양의 게임 맵이 있습니다. 이 맵에는 내구도를 가진 건물이 각 칸마다 하나씩 있습니다. 적은 이 건물들을 공격하여 파괴하려고 합니다. 건물은 적의 공격을 받으면 내구도가 감소하고 내구도가 0이하가 되면 파괴됩니다. 반대로, 아군은 회복 스킬을 사용하여 건물들의 내구도를 높이려고 합니다.

적의 공격과 아군의 회복 스킬은 항상 직사각형 모양입니다.
예를 들어, 아래 사진은 크기가 4 x 5인 맵에 내구도가 5인 건물들이 있는 상태입니다.

첫 번째로 적이 맵의 (0,0)부터 (3,4)까지 공격하여 4만큼 건물의 내구도를 낮추면 아래와 같은 상태가 됩니다.

두 번째로 적이 맵의 (2,0)부터 (2,3)까지 공격하여 2만큼 건물의 내구도를 낮추면 아래와 같이 4개의 건물이 파괴되는 상태가 됩니다.

세 번째로 아군이 맵의 (1,0)부터 (3,1)까지 회복하여 2만큼 건물의 내구도를 높이면 아래와 같이 2개의 건물이 파괴되었다가 복구되고 2개의 건물만 파괴되어있는 상태가 됩니다.

마지막으로 적이 맵의 (0,1)부터 (3,3)까지 공격하여 1만큼 건물의 내구도를 낮추면 아래와 같이 8개의 건물이 더 파괴되어 총 10개의 건물이 파괴된 상태가 됩니다. (내구도가 0 이하가 된 이미 파괴된 건물도, 공격을 받으면 계속해서 내구도가 하락하는 것에 유의해주세요.)

최종적으로 총 10개의 건물이 파괴되지 않았습니다.

건물의 내구도를 나타내는 2차원 정수 배열 board와 적의 공격 혹은 아군의 회복 스킬을 나타내는 2차원 정수 배열 skill이 매개변수로 주어집니다. 적의 공격 혹은 아군의 회복 스킬이 모두 끝난 뒤 파괴되지 않은 건물의 개수를 return하는 solution함수를 완성해 주세요.

제한사항 

  • 1 ≤ board의 행의 길이 (= N) ≤ 1,000
  • 1 ≤ board의 열의 길이 (= M) ≤ 1,000
  • 1 ≤ board의 원소 (각 건물의 내구도) ≤ 1,000
  • 1 ≤ skill의 행의 길이 ≤ 250,000
  • skill의 열의 길이 = 6
  • skill의 각 행은 [type, r1, c1, r2, c2, degree]형태를 가지고 있습니다.
    • type은 1 혹은 2입니다.
    • type이 1일 경우는 적의 공격을 의미합니다. 건물의 내구도를 낮춥니다.
    • type이 2일 경우는 아군의 회복 스킬을 의미합니다. 건물의 내구도를 높입니다.
    • (r1, c1)부터 (r2, c2)까지 직사각형 모양의 범위 안에 있는 건물의 내구도를 degree 만큼 낮추거나 높인다는 뜻입니다.
      • 0 ≤ r1 ≤ r2 < board의 행의 길이
      • 0 ≤ c1 ≤ c2 < board의 열의 길이
      • 1 ≤ degree ≤ 500
      • type이 1이면 degree만큼 건물의 내구도를 낮춥니다.
      • type이 2이면 degree만큼 건물의 내구도를 높입니다.
  • 건물은 파괴되었다가 회복 스킬을 받아 내구도가 1이상이 되면 파괴되지 않은 상태가 됩니다. 즉, 최종적으로 건물의 내구도가 1이상이면 파괴되지 않은 건물입니다.
정확성 테스트 케이스 제한 사항
  • 1 ≤ board의 행의 길이 (= N) ≤ 100
  • 1 ≤ board의 열의 길이 (= M) ≤ 100
  • 1 ≤ board의 원소 (각 건물의 내구도) ≤ 100
  • 1 ≤ skill의 행의 길이 ≤ 100
  • 1 ≤ degree ≤ 100
효율성 테스트 케이스 제한 사항
  • 주어진 조건 외 추가 제한사항 없습니다.

입출력 예

board skill result
[[5,5,5,5,5],[5,5,5,5,5],[5,5,5,5,5],[5,5,5,5,5]] [[1,0,0,3,4,4],[1,2,0,2,3,2],[2,1,0,3,1,2],[1,0,1,3,3,1]] 10
[[1,2,3],[4,5,6],[7,8,9]] [[1,1,1,2,2,4],[1,0,0,1,1,2],[2,2,0,2,0,100]] 6

입출력 예 #1

문제 예시와 같습니다.

입출력 예 #2

<초기 맵 상태>

첫 번째로 적이 맵의 (1,1)부터 (2,2)까지 공격하여 4만큼 건물의 내구도를 낮추면 아래와 같은 상태가 됩니다.

두 번째로 적이 맵의 (0,0)부터 (1,1)까지 공격하여 2만큼 건물의 내구도를 낮추면 아래와 같은 상태가 됩니다.

마지막으로 아군이 맵의 (2,0)부터 (2,0)까지 회복하여 100만큼 건물의 내구도를 높이면 아래와 같은 상황이 됩니다.

총, 6개의 건물이 파괴되지 않았습니다. 따라서 6을 return 해야 합니다.

제한시간 안내

  • 정확성 테스트 : 10초
  • 효율성 테스트 : 언어별로 작성된 정답 코드의 실행 시간의 적정 배수

 

첫번째 제출 코드 (정확성만 맞음)

def solution(board, skill):
    answer = 0
    h = len(board)
    w = len(board[0])
    for [t, r1, c1, r2, c2, d] in skill:
        if t == 1:
            #공격
            for i in range(r1, r2+1):
                for j in range(c1, c2+1):
                    board[i][j] -= d
        else:
            for i in range(r1, r2+1):
                for j in range(c1, c2+1):
                    board[i][j] += d 
    for i in range(h):
        for j in range(w):
            if board[i][j] > 0:
                answer += 1
    return answer

도저히 6번 문제에 해당하는 풀이가 생각나지 않아 일단 가장 간단한 풀이로 풀어보았습니다.

정확성은 맞았지만 도저히 250,000 * 1,000,000을 줄일 방법이 생각나지 않아 검색을 통해 방법을 찾았습니다.

 

두번째 제출 코드(정답)

def solution(board, skill):
    answer = 0 
    h = len(board)
    w = len(board[0])
    temp = [[0] * w for _ in range(h)] 
    for [t, r1, c1, r2, c2, d] in skill:
        if t == 1: # 공격 
            temp[r1][c1] -= d
            if r2+1 < h and c2+1 < w:
                temp[r2+1][c2+1] -= d
            if r2+1 < h:
                temp[r2+1][c1] += d
            if c2+1 < w:
                temp[r1][c2+1] += d             
        else:
            temp[r1][c1] += d
            if r2+1 < h and c2+1 < w:
                temp[r2+1][c2+1] += d
            if r2+1 < h:
                temp[r2+1][c1] -= d
            if c2+1 < w:
                temp[r1][c2+1] -= d
        
    for i in range(h):
        for j in range(1, w):
            temp[i][j] += temp[i][j-1]
            
    for j in range(w):
        for i in range(1, h):
            temp[i][j] += temp[i-1][j]
    
    for i in range(h):
        for j in range(w):
            board[i][j] += temp[i][j]
            if board[i][j] > 0:
                answer += 1
            
    return answer

 

먼저 BaaarkingDog 님의 강의를 통해 구글링 하는 법을 배워서 "2D update query constant"라는 방법을 배워 구체적으로 코드포스의 prefix sum 방법을 통해 문제를 해결할 수 있었습니다.

위 방법은 codeforces의 [Tutorial] 1D and 2D constant time per query range updates (a.k.a difference arrays)에 자세히 나와있습니다.

간략하게 설명하면 DP를 이용한 방법입니다. 시작점만 +degree 인지 -degree인지 표현하고 누적의 아래 끝 오른쪽 끝 대각선 끝을 구분지어 degree를 누적합니다.

그 후 원래 board를 dp테이블(temp)과 합칩니다. 

첫번째 예제를 dp테이블에 임시로 누적합 한 결과
board 결과

결과 및 정리

저 혼자 풀었다면 시간을 하루 줘도 못 풀 어려운 문제였습니다. 

백준의 11660 문제와 비슷하다고 하니 백준에 있는 비슷한 문제들도 한 번 풀어봐야겠습니다.

그리고 다음에 비슷한 문제가 나온다면 꼭 맞추고 싶습니다.

반응형