
문제 출처 : https://www.acmicpc.net/problem/3015
N명이 한 줄로 서 있고, 서로 볼 수 있는 쌍의 수를 구하는 문제.
예시 입력
7 # 사람 수
2
4
1
2
2
5
1
정답은 10. 직관적으로 세어보면,
(N - 1) 쌍이 기본.내가 처음 손으로 센 쌍들:
2-4, 4-1, 1-2, 2-2, 2-5, 5-1 → 6쌍
여기에,
4-(1)-2, 4-(1,2)-2, 4-(1,2,2)-5, 2-(2)-5 → 4쌍
합쳐서 10쌍.
N ≤ 500,000 이라서 O(N^2) 는 절대 안 됨.
한 번의 스캔으로 끝내는 O(N) 풀이가 필요하고, 모노톤(단조) 스택이라는 방법을 써야 한다.
스택에는 현재 후보보다 앞에 있는 사람 중에서 아직 뒤 사람이랑 연결될 가능성이 남아 있는 후보들의 (height, count) 를 넣는다.
여기서 count 는 “같은 키(height)가 연속으로 몇 명 있는지”의 개수다. (예: ..., (2,3)이라면 키 2가 연속 3명)
새 사람의 키 h를 처리할 때 규칙:
1) 스택 top의 키가 h보다 작은 경우
top.height < h 인 동안 pop 하면서 그 그룹의 count만큼 쌍을 더한다. h와 서로 볼 수 있고, h가 오면서 가려지므로 이제 기회가 끝난다.2) 스택 top의 키가 h와 같은 경우
c를 pop 하고, 정답 += c (서로 다 보임) count += c 3) 스택 top의 키가 h보다 큰 경우
마지막에 (h, count) 를 스택에 push.
이렇게 하면 각 사람(각 노드)은 최대 한 번 push/pop 되므로 총 O(N) 으로 풀 수 있다.
import sys
input = sys.stdin.readline
N = int(input().strip()) # 사람 수
stack = [] # (height 키 , count 같은 키 연속 개수) 형태로 스택에 저장 할 것임
ans = 0 # 서로 볼 수 있는 쌍의 개수
for _ in range(N): # N명의 사람을 왼쪽에서 오른쪽으로 순차 처리할 것임
h = int(input().strip()) # 키 입력받음
cnt = 1 # 현재 키 h를 가진 사람의 연속 개수, 초기값 1명
# 1) 스택 top의 키가 이제 들어오는 현재 키보다 작은 경우,
while stack and stack[-1][0] < h:
ans += stack[-1][1] # # 해당 stack의 그룹들의 인원 수 만큼 ans에 쌍 개수를 추가
stack.pop() # 가려져서 더 이상 뒤와 연결될 수 없으므로 제거
if stack and stack[-1][0] == h: # 같은 키 그룹이 바로 뒤에 오는 경우
c = stack[-1][1]
ans += c # 같은 키 끼리는 전부 볼 수 있으므로 c만큼 쌍 추가
stack.pop() # 기존 같은 키 그룹은 병합할 것이므로 제거
cnt += c # 현재 사람까지 포함해 같은 키 연속 개수 갱신
if stack: # 같은 키 그룹 뒤에 더 큰 키가 남아 있다면
ans += 1 # 그 더 큰 사람 1명과도 서로 볼 수 있으므로 +1
else:
if stack:# 스택이 비었거나, top의 키가 이제 오는 현재 키 보다 더 큰 경우
ans += 1 # 바로 앞의 더 큰 사람 1명과는 서로 볼 수 있으므로 +1
stack.append((h,cnt))
print(ans)
스택 표기: [(키, 개수)], ans는 누적 쌍 수
1) h=2
2) h=4
3) h=1
4) h=2
5) h=2
6) h=5
7) h=1
최종 ans = 10
stack에 튜플형식으로 저장하며 푸는 단조스택 문제였다.
모노톤(단조) 스택에 대해 정리하고 다시 봐야겠다.