백준

백준 3015번: 오아시스 재결합 (Stack)

츄츄츄츄츄츄츄 2023. 1. 20. 11:12

문제 링크:3015번: 오아시스 재결합 (acmicpc.net)

 

3015번: 오아시스 재결합

첫째 줄에 줄에서 기다리고 있는 사람의 수 N이 주어진다. (1 ≤ N ≤ 500,000) 둘째 줄부터 N개의 줄에는 각 사람의 키가 나노미터 단위로 주어진다. 모든 사람의 키는 231 나노미터 보다 작다. 사람

www.acmicpc.net

이 문제는 만약 N명이 한 줄로 서있고 각 사람이 각각 의 키를 갖고 있을때, 서로를 볼 수 있는 쌍을 구하는 문제이다. 여기서 주어진 조건은

 

1. 키는 2^31보다 작은 숫자로 주어진다.

2. 두 사람 A,B 가 서로를 볼 수 있음을 판별할때는 만약 A,B 사이에  A키 또는 B키보다 큰 사람이 한명이라도 있으면, 볼수 없다.

 

처음 이 문제를 봤을때는 방향성을 못잡아서 미루어두었는데, 스위핑이나 스택 문제들을 풀어보고 어느정도 감을 잡을 수 있었다. 먼저 i (0<=i<N) 번째 사람이 들어간 쌍을 구할 떄, 쌍을 구하는 방법은 (j < i < k) 라고 했을때 i보다 작은 범위의 j를 이용해  (j, i)끼리 묶는 것과 큰 k를 이용해 (i, k) 로 묶는 경우 두개가 있을 것이다.

 

만약 모든 인덱스마다 앞 뒤로 모두를 조사한다면 중복되는 쌍이 있을 것이므로 나는 i번째 사람을 조사할 때에는 i보다 작은 사람들과 묶는 쌍 ((j, i) 의 경우) 을 조사하기로 하였다.

 

예시로 3, 5, 4, 1, 2 로 키가 주어지고 현재 index 3번째 (1) 을 조사한다고 했을때, index 0 ~ 2까지 최대값인 5보다 작은 값들은 어차피 가려지므로 조사할 필요가 없다. 그러므로 나는 순서대로 각 인덱스를 탐색하면서 임의의 stack에 내림차순이 되는 방법으로 교체하고 채워나가는 방법을 사용했다.

 

처음에는 스택이 비어있으므로

1번째는 [3]

2번째는 5가 스택의 마지막 값보다 크므로 pop해주고 [5]

3번째는 4가 스택의 마지막 값보다 작으므로 추가해서 [5,4]

4번쨰는 1가 스택의 마지막 값보다 작으므로 추가해서 [5,4,1]

5번째는 2가 스택의 마지막 값보다 크므로 pop 해주고, 마지막 값이 4가되므로 작으므로 추가해준다. [5,4,2]

 

이런 순서대로 스택을 저장해주었다. 여기서 pop해준다는 뜻은 자신보다 작으므로 무조건 볼 수 있다는 뜻이므로 pop해주는 횟수를 cnt에 올려 저장해주고, 마지막에 stack에 추가할 때 stack이 비어있지 않다면 마지막 값은 볼 수 있으므로 구한 cnt에 +1해주면 해당 인덱스에서 가능한 순서쌍의 개수를 구할 수 있다.

import sys

N=int(sys.stdin.readline().rstrip())

table=[0 for _ in range(N)]
stack=[]

def stackup(cur): #new가 stack[-1][0] 보다 작거나 같을때
    if cur<stack[-1][0]:
        stack.append([cur,0])
        return 1

    else: #new가 stack[-1][0]과 같을때
        last,same=stack[-1]
        
        if same==len(stack)-1: #stack 내 모든 사람이 중복일 때
            stack.append([cur,same+1])
            return same+1
        
        else: #stack 내 마지막 (same+1)명이 중복일때
            stack.append([cur,same+1])
            return same+2


def replace(cur): #new가 stack[-1][0] 보다 클 떄
    cnt=0
    while stack: #자신보다 작은사람 안나올때까지 pop, pop횟수저장
        if stack[-1][0]<cur:
            stack.pop()
            cnt+=1
            
        else: #중간에 크거나 같은사람 만난경우
            return cnt + stackup(cur)
        
    stack.append([cur,0])
    return cnt #stack내 모든 요소 pop 한경우
        
for i in range(N):
    new=int(sys.stdin.readline().rstrip())

    if not stack:
        stack.append([new,0]) #중복되는 키 사람들 처리 위해 [키,중복갯수-1] 형태로저장
        continue

    if new<=stack[-1][0]:
        table[i]=stackup(new)

    else:
        table[i]=replace(new)
        
print(sum(table))

기본적인 원리는 위와 같지만 여기서 같은 키를 가진 사람이 입력으로 주어질 수 있다. 같은 키를 가진 사람을 처리하기 위해서 stack에 [키, 중복인원] 형태로 저장해 나갔다. 만약 중복이 아니라면 [키,0] 1회 중복됐다면 (2명 같은키) [키,1] 이런 식으로 저장해 같은 키의 인원을 직전 값만 봐도 바로 알 수 있도록 하였다.

 

새로 추가되는 것이 stack의 마지막 키보다 작거나 같다면 바로 stackup() 해준다. stackup() 함수를 통해 만약 추가되는 값이 마지막 값보다 작다면 [new,0] 으로 바로 추가해주고 볼수 있는 사람은 직전의 한명이므로 return 1 해준다. 만약 마지막 값과 같다면 직전값의 same을 보아 모든 사람이 중복이라면 중복 시작 이전의 값이 없으므로 (same+1), 일부가 중복이라면 (same+1) + 1 (중복 시작 이전의 마지막 값) 을 return해준다.

 

replace()함수는 new 값이 stack의 마지막 키보다 클 때 시행해준다. stack을 pop해가며 stack의 마지막 키보다 new가 작거나 같아질 때까지 반복한다. pop하다가 만약 자신보다 큰 마지막 키를 발견했다면 여기서 기존의 stackup() 조건을 충족하므로 cnt  + strack() 을 리턴하여 반복을 줄였다. 만약 stack을 모두 pop했다면 cnt만큼 return해주면 된다. 

 

이렇게 하면 table에 각 인덱스를 조사할 때 마다 쌍의 개수가 저장된다. sum해주면 답이 나온다