본문 바로가기
Algorithm/구현

백준 21608-상어초등학교

by brother_stone 2022. 9. 26.

 

 

21608번: 상어 초등학교

상어 초등학교에는 교실이 하나 있고, 교실은 N×N 크기의 격자로 나타낼 수 있다. 학교에 다니는 학생의 수는 N2명이다. 오늘은 모든 학생의 자리를 정하는 날이다. 학생은 1번부터 N2번까지 번호

www.acmicpc.net

문제

상어 초등학교에는 교실이 하나 있고, 교실은 N×N 크기의 격자로 나타낼 수 있다. 학교에 다니는 학생의 수는 N2명이다. 오늘은 모든 학생의 자리를 정하는 날이다. 학생은 1번부터 N2번까지 번호가 매겨져 있고, (r, c)는 r행 c열을 의미한다. 교실의 가장 왼쪽 윗 칸은 (1, 1)이고, 가장 오른쪽 아랫 칸은 (N, N)이다.

선생님은 학생의 순서를 정했고, 각 학생이 좋아하는 학생 4명도 모두 조사했다. 이제 다음과 같은 규칙을 이용해 정해진 순서대로 학생의 자리를 정하려고 한다. 한 칸에는 학생 한 명의 자리만 있을 수 있고, |r1 - r2| + |c1 - c2| = 1을 만족하는 두 칸이 (r1, c1)과 (r2, c2)를 인접하다고 한다.

  1. 비어있는 칸 중에서 좋아하는 학생이 인접한 칸에 가장 많은 칸으로 자리를 정한다.
  2. 1을 만족하는 칸이 여러 개이면, 인접한 칸 중에서 비어있는 칸이 가장 많은 칸으로 자리를 정한다.
  3. 2를 만족하는 칸도 여러 개인 경우에는 행의 번호가 가장 작은 칸으로, 그러한 칸도 여러 개이면 열의 번호가 가장 작은 칸으로 자리를 정한다.

예를 들어, N = 3이고, 학생 N2명의 순서와 각 학생이 좋아하는 학생이 다음과 같은 경우를 생각해보자.

학생의 번호좋아하는 학생의 번호
4 2, 5, 1, 7
3 1, 9, 4, 5
9 8, 1, 2, 3
8 1, 9, 3, 4
7 2, 3, 4, 8
1 9, 2, 5, 7
6 5, 2, 3, 4
5 1, 9, 2, 8
2 9, 3, 1, 4

가장 먼저, 4번 학생의 자리를 정해야 한다. 현재 교실의 모든 칸은 빈 칸이다. 2번 조건에 의해 인접한 칸 중에서 비어있는 칸이 가장 많은 칸인 (2, 2)이 4번 학생의 자리가 된다.

     
  4  
     

다음 학생은 3번이다. 1번 조건을 만족하는 칸은 (1, 2), (2, 1), (2, 3), (3, 2) 이다. 이 칸은 모두 비어있는 인접한 칸이 2개이다. 따라서, 3번 조건에 의해 (1, 2)가 3번 학생의 자리가 된다.

  3  
  4  
     

다음은 9번 학생이다. 9번 학생이 좋아하는 학생의 번호는 8, 1, 2, 3이고, 이 중에 3은 자리에 앉아있다. 좋아하는 학생이 가장 많이 인접한 칸은 (1, 1), (1, 3)이다. 두 칸 모두 비어있는 인접한 칸이 1개이고, 행의 번호도 1이다. 따라서, 3번 조건에 의해 (1, 1)이 9번 학생의 자리가 된다.

9 3  
  4  
     

이번에 자리를 정할 학생은 8번 학생이다. (2, 1)이 8번 학생이 좋아하는 학생과 가장 많이 인접한 칸이기 때문에, 여기가 그 학생의 자리이다.

9 3  
8 4  
     

7번 학생의 자리를 정해보자. 1번 조건을 만족하는 칸은 (1, 3), (2, 3), (3, 1), (3, 2)로 총 4개가 있고, 비어있는 칸과 가장 많이 인접한 칸은 (2, 3), (3, 2)이다. 행의 번호가 작은 (2, 3)이 7번 학생의 자리가 된다.

9 3  
8 4 7
     

이런식으로 학생의 자리를 모두 정하면 다음과 같다.

9 3 2
8 4 7
5 6 1

이제 학생의 만족도를 구해야 한다. 학생의 만족도는 자리 배치가 모두 끝난 후에 구할 수 있다. 학생의 만족도를 구하려면 그 학생과 인접한 칸에 앉은 좋아하는 학생의 수를 구해야 한다. 그 값이 0이면 학생의 만족도는 0, 1이면 1, 2이면 10, 3이면 100, 4이면 1000이다.

학생의 만족도의 총 합을 구해보자.

입력

첫째 줄에 N이 주어진다. 둘째 줄부터 N2개의 줄에 학생의 번호와 그 학생이 좋아하는 학생 4명의 번호가 한 줄에 하나씩 선생님이 자리를 정할 순서대로 주어진다.

학생의 번호는 중복되지 않으며, 어떤 학생이 좋아하는 학생 4명은 모두 다른 학생으로 이루어져 있다. 입력으로 주어지는 학생의 번호, 좋아하는 학생의 번호는 N2보다 작거나 같은 자연수이다. 어떤 학생이 자기 자신을 좋아하는 경우는 없다.

출력

첫째 줄에 학생의 만족도의 총 합을 출력한다.

제한

  • 3 ≤ N ≤ 20

예제 입력 1

3
4 2 5 1 7
3 1 9 4 5
9 8 1 2 3
8 1 9 3 4
7 2 3 4 8
1 9 2 5 7
6 5 2 3 4
5 1 9 2 8
2 9 3 1 4

예제 출력 1

54

예제 입력 2

3
4 2 5 1 7
2 1 9 4 5
5 8 1 4 3
1 2 9 3 4
7 2 3 4 8
9 8 4 5 7
6 5 2 3 4
8 4 9 2 1
3 9 2 1 4

예제 출력 2

1053

내 코드

n = int(input())

# 교실 grid
cls = [[0] * n for _ in range(n)]
students = []
students_dict = {}

# 학생과 그 학생이 좋아하는 친구들을 입력 받음
for i in range(n ** 2):
    s_lst = list(map(int, input().split()))
    students.append(s_lst)
    students_dict[s_lst[0]] = s_lst[1:]
# 상 좌 하 우
dy = (-1, 0, 1, 0)
dx = (0, -1, 0, 1)


def is_valid(y, x):
    return 0 <= y <= n - 1 and 0 <= x <= n - 1


# 규칙 2에 따라 규칙 1이 여러 개이면, 인접한 칸 중 빈칸이 가장 많은 요소를 채택
def calc_empty(lst):
    res = []
    max_score = 0
    for l in range(len(lst)):
        score = 0
        r = lst[l][0]
        c = lst[l][1]
        for i in range(4):
            if is_valid(r + dy[i], c + dx[i]) and not cls[r + dy[i]][c + dx[i]]:
                score += 1
        # 현재 스코어가 맥스 스코어보다 크면
        if score > max_score:
            max_score = score
            res.clear()
            res.append([r, c])
        # 현재 스코어가 맥스와 같으면
        elif score == max_score:
            res.append([r, c])
    return res


def deploy(s, f_lst):
    chk = [[0] * n for _ in range(n)]
    # 규칙 1 비어있는 칸 & 좋아하는 학생이 인접한 칸에 가장 많아야
    # 체크리스트 채우는 반복문
    for r in range(n):
        for c in range(n):
            # 차있을 때
            if cls[r][c]:
                if cls[r][c] in f_lst:
                    chk[r][c] = 'f'
                else:
                    chk[r][c] = -1
    # print(chk)
    for r in range(n):
        for c in range(n):
            # 체크리스트의 현 요소가 빈칸 일 때
            if not chk[r][c]:
                for i in range(4):
                    # 범위를 벗어나지 않고, 인접한 칸에 친한 친구가 있을 때
                    if is_valid(r + dy[i], c + dx[i]) and chk[r + dy[i]][c + dx[i]] == 'f':
                        # 현재 칸 += 1
                        chk[r][c] += 1
    # print(chk)

    max_score = -1
    max_score_lst = []
    # 또 돌면서 가장 점수가 높은 자리를 max_score_lst에 삽입
    for r in range(n):
        for c in range(n):
            if type(chk[r][c]) == int and chk[r][c] > max_score:
                max_score = chk[r][c]
                max_score_lst.clear()
                max_score_lst.append([r, c])
            elif chk[r][c] == max_score:
                max_score_lst.append([r, c])
    # 가장 높은 점수가 하나일 때
    if len(max_score_lst) == 1:
        r = max_score_lst[0][0]
        c = max_score_lst[0][1]
        cls[r][c] = s
    # 가장 높은 점수가 둘 이상일 때
    elif len(max_score_lst) > 1:
        res = calc_empty(max_score_lst)
        if len(res) == 1:
            r = res[0][0]
            c = res[0][1]
            cls[r][c] = s
        elif len(res) > 1:
            # sort는 첫번째 요소기준으로 정렬 하지만 첫번째 요소가 같으면 두번째 요소를 기준으로 정렬하므로 정렬 후 첫번째 요소를 정답으로 채택
            res.sort()
            r = res[0][0]
            c = res[0][1]
            cls[r][c] = s


for student in students:
    deploy(student[0], student[1:])


def calc_satisfaction():
    satisfaction = 0
    for r in range(n):
        for c in range(n):
            curr_stu = cls[r][c]
            cnt = 0
            for i in range(4):
                # 범위를 벗어나지 않고, 인접한 요소가 친한친구일 때
                if is_valid(r + dy[i], c + dx[i]) and cls[r + dy[i]][c + dx[i]] in students_dict[curr_stu]:
                    cnt += 1

            if cnt == 1:
                satisfaction += 1
            elif cnt == 2:
                satisfaction += 10
            elif cnt == 3:
                satisfaction += 100
            elif cnt == 4:
                satisfaction += 1000
    return satisfaction


print(calc_satisfaction())

 

피드백

특별한 알고리즘을 쓴다기 보다 문제의 조건을 빠르고 정확하게 독해해서 실수없이 코드로 구현하는게 중요한 문제였다.

이런 문제를 대충읽다가 하나를 놓치게 되면 돌이킬 수 없으니 시간이 오래걸리더라 완벽하게 이해가 될 때까지 꼼꼼히 읽는 게 중요해보인다.

 

풀이 자체가 오래 걸려 효율적이고 가독성 좋게 코드를 짜진 못한 것 같다. 다시 한번 풀어볼 때 간략하게 짜보면서 풀이 시간도 줄여보도록 하자.

 

그리고 이번 문제에서 실수 했던 점은 코드의 마지막 부분쯤 만족도를 계산하는 함수에서 인접한 친한친구가 0명일 때부터 4명일 때까지 점수가 달라지는데

if cnt == 1:
    satisfaction += 1
elif cnt == 2:
    satisfaction += 10
elif cnt == 3:
    satisfaction += 100
else:
    satisfaction += 1000

위와 같이 작성했다. 어차피 인접한 친구가 0명이면 0점이기 때문에 무심코 0일 때 경우를 생략했고 else문은 4명일 때만 분기될 거라고 생각해서 틀리게 됐다.

하지만 else문은 0일 때와 4명일 때, 두가지 경우에서 분기가 되기 때문에 0명일 때도 1000점이 더해지게 된다.

다풀어놓고 이런 실수하게 되면 나만 억울하니 더더욱 신경쓰도록 하자.

 

추가로 이번 문제를 풀면서 다시한번 확인하게 된 사실은 2차원 리스트 등을 정렬할 때 직접 key를 정의해주지 않는 한 첫번째 요소를 기준으로 먼저 정렬하고, 첫번째 요소가 같으면 그 때 두번째 요소로 정렬한다는 것이다.

예시를 한번 들어보자

lst = [[3,1],[1,2],[1,0],[2,2],[1,1]]
lst.sort()
print(sort) # [[1,0],[1,1],[1,2][2,2],[3,1]]

이 원리를 이용해서 같은 조건인 자리가 여러개 일 때, 행이 가장 작은 자리, 이마저도 여러 개면 열이 가장 작은 자리를 출력하는 코드를 구현했다. (ln. 93)

'Algorithm > 구현' 카테고리의 다른 글

[Java] 백준 14719 - 빗물  (0) 2024.01.23
백준 15787 - 기차가 어둠을 헤치고(파이썬)  (0) 2022.09.26