문제
제한/스펙
1 ≤ N ≤ 250,000
0 ≤ L_i ≤ 1e15
, 1 ≤ D_i ≤ 1e9
- 시간 제한 1초, 메모리 1GB →
O(N log N)
풀이 필요, 누적합은 64비트 정수 사용
입출력 형식/예제
예제 입력 1
예제 출력 1
예제 입력 2
1
2
3
4
5
| 4
0 1
0 2
0 3
0 4
|
예제 출력 2
접근 개요(아이디어 스케치)
- 관찰: 어떤 시점의 고도
h
는 지금까지 사용한 풍선들의 D
합이다. 풍선 i
를 사용할 수 있는 필요충분조건은 사용 직전 h ≤ L_i
. - 핵심 트릭: 각 풍선에 대해
E_i = L_i + D_i
를 정의하고 E
오름차순으로 본다. 이 순서의 접두집합에서 선택한 풍선들의 D
누적합이 항상 현재 E
를 넘지 않게 유지하면, 선택 집합을 어떤 순서로든 실제로 수행할 수 있다. - 그리디:
E
오름차순으로 순회하며 D
를 최대 힙에 넣고 누적합 S
를 더한다. S > E
가 되면 가장 큰 D
하나를 제거하여 S
를 줄인다. 이렇게 하면 같은 접두집합에서 가능한 한 많은 개수를 유지한다. - 정당성(교환 논법 요지): 현재 접두집합에서 누적합이
E
를 넘을 때 제거할 항목으로 가장 큰 D
를 빼면, 이후 어떤 E
에 대해서도 제약을 더 느슨하게 만들어(또는 같게 유지하여) 향후 선택 가능성을 최대화한다. 따라서 선택 개수를 최대로 하는 전략이다.
graph TD
A[입력 N, (L_i, D_i)] --> B[E_i = L_i + D_i 계산]
B --> C[E 오름차순 정렬]
C --> D{다음 풍선}
D -->|push D_i, S+=D_i| E[최대 힙 유지]
E --> F{S > E_i?}
F -->|Yes| G[힙에서 가장 큰 D 제거, S-=max]
F -->|No| H[유지]
G --> D
H --> D
알고리즘 설계
- 상태: 누적 상승량
S
, 최대 힙 PQ
(선택한 풍선들의 D
저장) - 절차:
- 모든 풍선에 대해
E=L+D
를 계산하고 E
오름차순으로 정렬 - 각 원소를 순회하며
D
를 PQ
에 넣고 S += D
S > E
이면 PQ
에서 가장 큰 D
를 꺼내 S
에서 빼기- 순회 종료 시
PQ
크기가 사용할 수 있는 최대 개수
- 올바름 근거: 접두집합에서
S ≤ 현재 E
불변식을 유지. 위반 시 가장 큰 D
제거가 이후 모든 E
에 가장 유리. 선택 집합에 대해 실제 수행 순서는 “사용 직전 고도 ≤ L”을 만족하도록 구성 가능.
복잡도
- 시간: 정렬
O(N log N)
+ 힙 연산 O(N log N)
→ 전체 O(N log N)
- 공간: 힙
O(N)
구현 (C++)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
| // 더 많은 정보는 42jerrykim.github.io 에서 확인하세요.
#include <bits/stdc++.h>
using namespace std;
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int N; if(!(cin >> N)) return 0;
vector<pair<long long,long long>> a; a.reserve(N);
for(int i=0;i<N;++i){
long long L,D; cin >> L >> D;
a.emplace_back(L + D, D); // (E, D)
}
sort(a.begin(), a.end()); // by E asc
priority_queue<long long> pq; // store D (max-heap)
long long sumD = 0;
for(const auto &p : a){
long long E = p.first, D = p.second;
pq.push(D);
sumD += D;
if(sumD > E){
sumD -= pq.top();
pq.pop();
}
}
cout << (int)pq.size() << '\n';
return 0;
}
|
구현 (Python)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
| # 더 많은 정보는 42jerrykim.github.io 에서 확인하세요.
import sys
import heapq
def solve() -> None:
data = sys.stdin.read().strip().split()
it = iter(data)
try:
n = int(next(it))
except StopIteration:
return
balloons = [] # (E, D)
for _ in range(n):
L = int(next(it)); D = int(next(it))
balloons.append((L + D, D))
balloons.sort() # by E asc
max_heap = [] # store -D to simulate max-heap
sum_d = 0
for E, D in balloons:
heapq.heappush(max_heap, -D)
sum_d += D
if sum_d > E:
largest = -heapq.heappop(max_heap)
sum_d -= largest
print(len(max_heap))
if __name__ == "__main__":
solve()
|
코너 케이스 체크리스트
L_i = 0
만 있는 경우: 첫 풍선만 가능 → 최대 1- 매우 큰
L_i
(최대 1e15
)와 큰 D_i
(최대 1e9
) 혼재: sumD
는 64비트(long long
) 필요 - 같은
E
가 다수: 정렬 안정성에 영향 없음(임의 순서 가능) N
이 큰 케이스(25만): 입출력 가속(빠른 IO), O(N log N)
구현 필수- 모든 풍선을 선택 가능한 경우: 힙 크기
N
- 한 개도 선택 불가한 구성: 답
0
제출 전 점검
- 64비트 정수 사용 여부(
long long
) - 힙에서 제거 시 누적합 업데이트 정확성
- 입력 파싱/개행 처리, 빠른 입출력 설정
- 정렬 키가
E=L+D
가 맞는지 재확인
참고자료/유사문제
- 데드라인 기반 작업 최대 개수 그리디(접두집합 누적시간 ≤ 데드라인, 위반 시 가장 긴 작업 제거)와 동일한 증명 틀을 사용. 여기서는 시작 제약을
E=L+D
로 치환하여 적용.