Published on
👁️

순회강연 ⭐

Authors
  • avatar
    Name
    River
    Twitter
순회강연문제 보기
Gold3
백준📅 2025-07-09
그리디우선순위 큐정렬
문제 설명

한 저명한 학자에게 n(0 ≤ n ≤ 10,000)개의 대학에서 강연 요청을 해 왔다. 각 대학에서는 d(1 ≤ d ≤ 10,000)일 안에 와서 강연을 해 주면 p(1 ≤ p ≤ 10,000)만큼의 강연료를 지불하겠다고 알려왔다. 각 대학에서 제시하는 d와 p값은 서로 다를 수도 있다. 이 학자는 이를 바탕으로, 가장 많은 돈을 벌 수 있도록 순회강연을 하려 한다. 강연의 특성상, 이 학자는 하루에 최대 한 곳에서만 강연을 할 수 있다.

예를 들어 네 대학에서 제시한 p값이 각각 50, 10, 20, 30이고, d값이 차례로 2, 1, 2, 1 이라고 하자. 이럴 때에는 첫째 날에 4번 대학에서 강연을 하고, 둘째 날에 1번 대학에서 강연을 하면 80만큼의 돈을 벌 수 있다.

제한사항
  • n의 범위는 0 이상 10,000 이하
  • p(강연료)의 범위는 1 이상 10,000 이하
  • d(마감일)의 범위는 1 이상 10,000 이하
입출력 예
입력
7
20 1
2 1
10 3
100 2
8 2
5 20
50 10
출력
185
설명

각 강연을 마감일 순으로 정렬하여 우선순위 큐로 최적화하면 185의 최대 강연료를 얻을 수 있습니다.

풀이 접근법
시간복잡도 : O(N log N)
공간복잡도 : O(N)
사용 알고리즘 :
우선순위 큐(Min Heap)그리디정렬
핵심 포인트 :
  • 마감일 순으로 정렬하여 순차적으로 처리
  • 우선순위 큐로 현재까지의 최적 선택 유지
  • 큐 크기가 마감일을 초과하면 가장 작은 강연료 제거
  • 두 가지 접근 방법: 마감일 기준 vs 강연료 기준
경계값/반례 :
  • 모든 강연의 마감일이 1일인 경우: 가장 비싼 강연 하나만 선택
  • 마감일이 모두 다른 경우: 모든 강연을 다 할 수 있음
  • n이 0인 경우: 0 출력

핵심 아이디어

  • 방법 1

    마감일이 빠른 순서로 정렬한 후, 각 강연을 순차적으로 검토하면서 현재까지 선택할 수 있는 최적의 강연 집합을 유지한다.

  • 방법 2

    강연료 기준 내림차순 정렬한 후, 날짜 자리 배치를 통해 해당 강연의 마감일에 가까운 순서부터 채워나가는 방법

내부 해부 과정
lectures = [[20, 1], [2, 1], [10, 3], [100, 2], [8, 2], [5, 20], [50, 10]]
  1. 7개의 데이터가 들어온다.
  2. lectures = [ [20, 1], [2, 1], [10, 3], [100, 2], [8, 2], [5, 20], [50, 10] ]로 데이터가 들어온다. 첫번째는 강연료이고, 두번째는 마감일로 된 배열이다.
  3. 강연료를 기준으로 내림차순 정렬을 한다.
  4. lectures = [ [100, 2], [50, 10], [20, 1], [10, 3], [8, 2], [5, 20], [2, 1] ]
  5. 최대한 이득이 되도록 하려면, 마감일이 작은 애들 중 높은 애를 선택해야 한다.
  6. 첫번째 날, 마감일이 1인 데이터인 [20, 1], [2, 1][20, 1]을 택하고 [2, 1]은 버려진다.
    • sum = 20
  7. 2번째 날, 마감일이 2인 데이터인 [100, 2], [8, 2][100, 2]을 택하고 [8, 2]은 버려진다.
    • sum = 120
  8. 3번째 날, 마감일이 3인 데이터는 [10, 3] 뿐이다.
    • sum = 130
  9. 4번째 날, 마감일이 4인 데이터는 없다. 다음으로 다가오는 제일 작은 마감일을 가진 것은 [50, 10]이다.
    • sum = 180
  10. 5번째 날, 그 다음으로 작은 마감일은 [5, 20]이다.
    • sum = 185

⚠️ 그리디 함정

  • 위의 내부 해부 과정은 함정에 걸린 상태
  • lectures = [[10, 1], [100, 2], [150, 2]]
  • 위의 로직으로는 1번째 날에 마감일이 1인 것은 1개이므로 10을 채택하고, 2번째 날은 마감일이 2인 것 중 큰 150을 채택한다.
  • 그렇지만 첫번째 날 100을 하고 두번째 날 150을 하면 더욱 돈을 더 벌 수 있다는 것을 제외한 것이다.
생각 과정

❌ 잘못된 접근 (함정)

  1. 강연료를 기준으로 내림차순 정렬
  2. 마감일이 같은 것들 중 가장 비싼 것만 선택
  3. 문제점: lectures = [[10, 1], [100, 2], [150, 2]]의 경우
    • 잘못된 로직: 1일차에 10, 2일차에 150 선택 → 160
    • 올바른 답: 1일차에 100, 2일차에 150 선택 → 250

⭕ 방법 1 (boolean 배열 방식)

  1. 마감일이 겹치면 강연료가 제일 큰 것을 택해야 하지만, 이후 더 큰 마감일을 가진 것 중에 버려지는 것이 있다면 그것을 대신 택해야 한다.

  2. 마감일이 낮은 순서로 처리하면 미래에 일어나는 일을 계산하는 것이 쉽지 않다.

  3. 결국 무엇을 포기해야 하는 것이므로 비싼 것을 위주로 선택해야 한다.

  4. 강연료를 기준으로 내림차순 정렬을 한다.

  5. boolean[]로 해당 날짜에 주인이 있는지 여부를 선택한다. 주인 있다면 정렬이 되었으므로, 무조건 주인의 강연료 이상일 수 밖에 없다. boolean[]의 크기는 최대 마감일로 설정한다. 그 이상은 될 수 없다.

  6. 즉, 해당 날짜에 주인이 있다면 그 날짜는 넘어가야 한다.

  7. 비싼 강연이라도 최대한 마감일에 가깝게 처리해야 더 돈을 많이 받을 수 있다.

    즉, 마감일부터 줄여가며 반복을 돌아야 한다.

💡 추가 설명

  • 이 방법의 핵심은 "가능한 가장 늦은 날짜에 배치"하는 그리디 전략이다. 비싼 강연을 마감일에 가깝게 배치함으로써 다른 강연들이 들어갈 수 있는 기회를 최대한 보장한다.

    // 예시: [100, 3] 강연의 경우
    for (int j = 3; j > 0; j--) { // 3일 → 2일 → 1일 순으로 확인
        if (!isScheduled[j - 1]) {
            isScheduled[j - 1] = true; // 가능한 가장 늦은 날짜에 배치
            break;
        }
    }
    

⭕ 방법 2 (우선순위 큐 방식)

  1. 내부 해부 과정에서 문제는 마감일이 낮은 순서로 정렬 후 가장 높은 강연을 택하면 이후에 버려지는 기회 중 더 좋은 기회를 놓치게 된다.

  2. 그렇다면, 마감일이 낮은 순서로 정렬 후 높은 강연을 택하는 것은 유지하고, 이것을 확정하지 않고 추후 더 좋은 것이 나오면 기존의 것을 버리면 된다.

  3. 이전의 풀었던 명예의 전당처럼 베스트 멤버를 유지하는 것처럼, 마감일이 낮은 순서로 정렬된 배열에서 하나씩 꺼내서 강연료 오름차순 우선순위 큐에 넣는다.

  4. 베스트 멤버의 길이는 해당 날짜이다. 길이가 4일이면 4일차로, 더 좋은 경우의 수를 유지할 수 있다.

💡 추가 설명

  • 이 방법은 "동적 최적화"이다. 마감일을 하나의 체크포인트로 생각하면, 각 체크포인트에서 "지금까지 선택할 수 있는 최고의 조합"을 계속 업데이트하는 것.

    // 핵심 로직
    if (lectures[i][1] < pq.size()) {
        pq.poll(); // 현재까지 선택된 것 중 가장 작은 것을 제거
    }
    

💡 왜 이 조건이 중요한가?

  • 마감일이 3이면 최대 3개의 강연만 할 수 있음
  • 큐 크기가 3을 초과하면 불가능한 상황이므로 가장 작은 것을 버림

⭕ 실제 데이터 흐름 (방법 2)

  • 7개의 데이터가 들어온다.
  • lectures = [[20, 1], [2, 1], [10, 3], [100, 2], [8, 2], [5, 20], [50, 10]]로 데이터가 들어온다.
  • 마감일을 기준으로 오름차순 정렬한다.
  • lectures = [[20, 1], [2, 1], [100, 2], [8, 2], [10, 3], [50, 10], [5, 20]]
  • 각 강연을 순차적으로 처리
    • [20, 1]: 큐에 20 추가, 큐 크기(1) ≤ 마감일(1) → 유지
    • [2, 1]: 큐에 2 추가, 큐 크기(2) > 마감일(1) → 최솟값 2 제거
    • [100, 2]: 큐에 100 추가, 큐 크기(2) ≤ 마감일(2) → 유지
    • [8, 2]: 큐에 8 추가, 큐 크기(3) > 마감일(2) → 최솟값 8 제거
    • [10, 3]: 큐에 10 추가, 큐 크기(3) ≤ 마감일(3) → 유지
    • [50, 10]: 큐에 50 추가, 큐 크기(4) ≤ 마감일(10) → 유지
    • [5, 20]: 큐에 5 추가, 큐 크기(5) ≤ 마감일(20) → 유지
  • 최종 큐 : [10, 20, 50, 100, 5]
  • 합계 : 10 + 20 + 50 + 100 + 5 = 185

💡 추가 인사이트

  • 이 과정에서 주목할 점은 [2, 1][8, 2]가 제거되었다. 이들은 각각의 마감일 시점에서 "최선이 아닌 선택"이었기 때문이다.
  • 이처럼 우선순위 큐는 **"지금까지의 최선"**을 계속 갱신하면서 최적해를 찾아간다.

💡 두 방법의 시간복잡도 비교

  • 방법 1 (boolean 배열) : O(N × D) - D는 최대 마감일
  • 방법 2 (우선순위 큐) : O(N log N) - 일반적으로 더 효율적
핵심 패턴

1️⃣ 우선순위 큐의 활용

PriorityQueue<Integer> pq = new PriorityQueue<>((a, b) -> a - b);
  • 최소힙을 사용하여 가장 작은 강연료를 쉽게 제거
  • 동적으로 최적 집합을 유지하는 것이 핵심

2️⃣ 마감일 순 정렬의 중요성

Arrays.sort(lectures, (a, b) -> a[1] - b[1]);
  • 시간 순서대로 처리해야 올바른 그리디 선택 가능
  • 마감일이 급한 것들을 먼저 처리하여 후에 더 비싼 것들로 교체 가능
  • 강연료 순 정렬은 지역 최적해에 빠질 수 있음

3️⃣ 크기 조절 로직 ⭐⭐⭐

if (lectures[i][1] < pq.size()) {
    pq.poll();
}
  • 현재 마감일보다 선택된 강연이 많으면 최솟값 제거
  • 항상 실현 가능한 최적해를 유지
  • 핵심: 마감일은 일종의 체크 타임으로, 마감일이 9라면 9일째까지 총 9개의 강의를 할 수 있음
  • 마감일보다 큐 크기가 크면 제일 작은 것 하나를 버려야 함
최종 제출 코드

⭕ 방법 1 : 우선순위 큐

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int[][] lectures = new int[n][2];

        for (int i = 0; i < n; i++) {
            lectures[i][0] = sc.nextInt(); // 강연료
            lectures[i][1] = sc.nextInt(); // 마감일
        }

        Arrays.sort(lectures, (a, b) -> a[1] - b[1]); // 마감일 순 정렬
        PriorityQueue<Integer> pq = new PriorityQueue<>(); // Min Heap

        for (int i = 0; i < n; i++) {
            pq.add(lectures[i][0]);

            if (lectures[i][1] < pq.size()) {
                pq.poll(); // 가장 작은 강연료 제거
            }
        }

        int sum = 0;
        while (!pq.isEmpty()) {
            sum += pq.poll();
        }

        System.out.println(sum);
        sc.close();
    }
}


⭕ 방법 2 : boolean 배열

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int[][] lectures = new int[n][2];

        for (int i = 0; i < n; i++) {
            lectures[i][0] = sc.nextInt();
            lectures[i][1] = sc.nextInt();
        }

        Arrays.sort(lectures, (a, b) -> b[0] - a[0]); // 강연료 순 정렬

        int lastDay = 0;
        for (int i = 0; i < lectures.length; i++) {
            if (lastDay <= lectures[i][1]) {
                lastDay = lectures[i][1];
            }
        }

        boolean[] isScheduled = new boolean[lastDay];

        int sum = 0;
        for (int i = 0; i < lectures.length; i++) {
            for (int j = lectures[i][1]; j > 0; j--) {
                if (!isScheduled[j - 1]) {
                    isScheduled[j - 1] = true;
                    sum += lectures[i][0];
                    break;
                }
            }
        }

        System.out.println(sum);
        sc.close();
    }
}
관련 문제
그리디정렬우선순위 큐
46문제
백준
백준
Gold3
순회강연
그리디우선순위 큐+1
백준
Gold5
회의실 배정
그리디정렬우선순위 큐
백준
Silver2미해결!
소가 길을 건너간 이유 3
그리디정렬
백준
Silver2미해결!
잃어버린 괄호
그리디문자열
백준
Silver1미해결!
물병
비트마스크그리디
백준
Silver1미해결!
신입 사원
그리디정렬
백준
Silver1미해결!
카드 합체 놀이
그리디우선순위 큐
백준
Gold5미해결!
강의실 배정
그리디정렬우선순위 큐
백준
Gold5미해결!
구간 자르기
투포인터정렬
백준
Gold5미해결!
두 용액
투포인터정렬
백준
Gold5미해결!
수 고르기
투포인터정렬
백준
Gold4미해결!
수 묶기
그리디정렬
백준
Gold4미해결!
좋다
투포인터정렬
백준
Gold4미해결!
휴게소 세우기
이분탐색그리디
백준
Gold3미해결!
같이 눈사람 만들래?
투포인터정렬
백준
Gold3미해결!
공주님의 정원
그리디정렬
백준
Gold3미해결!
과제
그리디우선순위 큐
백준
Gold3미해결!
세 용액
투포인터정렬
백준
Gold2미해결!
가운데를 말해요
우선순위 큐자료구조
백준
Gold2미해결!
공항
그리디유니온파인드
백준
Gold2미해결!
보석 도둑
그리디우선순위 큐정렬
백준
Gold2미해결!
싸지방에 간 준하
그리디우선순위 큐정렬
백준
Gold2미해결!
집배원 한상덕
투포인터BFS그리디
백준
Gold2미해결!
칵테일
DFS수학그리디
백준
Gold2미해결!
컵라면
그리디우선순위 큐
백준
Gold2미해결!
합이 0인 네 정수
투포인터해시정렬
백준
Gold1미해결!
멀티탭 스케줄링
그리디정렬
백준
Gold1미해결!
팰린드롬 분할
DP그리디문자열
프로그래머스
프로그래머스
Level4
무지의 먹방 라이브
그리디우선순위 큐효율성+1
프로그래머스
Level2
더 맵게
우선순위 큐그리디
프로그래머스
Level3
야근 지수
그리디우선순위 큐이분 탐색
프로그래머스
Level2
디펜스 게임
그리디우선순위 큐
프로그래머스
Level3
이중우선순위큐
그리디트리맵
프로그래머스
Level3
디스크 컨트롤러
그리디스케줄링우선순위 큐
프로그래머스
Level2미해결!
과제 진행하기
스택정렬시뮬레이션
프로그래머스
Level2미해결!
구명보트
그리디투포인터정렬
프로그래머스
Level2미해결!
조이스틱
그리디문자열
프로그래머스
Level2미해결!
큰 수 만들기
그리디스택
프로그래머스
Level3미해결!
기지국 설치
그리디구간 커버
프로그래머스
Level3미해결!
단속카메라
그리디정렬
프로그래머스
Level3미해결!
단어 변환
BFS그리디
프로그래머스
Level3미해결!
베스트앨범
해시정렬
프로그래머스
Level3미해결!
섬 연결하기
그리디MST유니온파인드
프로그래머스
Level3미해결!
정수 삼각형
DP그리디
프로그래머스
Level3미해결!
최고의 집합
그리디수학
프로그래머스
Level4미해결!
징검다리
이분탐색그리디