[백준 1417번 국회의원 선거] (알고리즘)
Heap
문제
다솜이는 사람의 마음을 읽을 수 있는 기계를 가지고 있다. 다솜이는 이 기계를 이용해서 2008년 4월 9일 국회의원 선거를 조작하려고 한다.
다솜이의 기계는 각 사람들이 누구를 찍을 지 미리 읽을 수 있다. 어떤 사람이 누구를 찍을 지 정했으면, 반드시 선거때 그 사람을 찍는다.
현재 형택구에 나온 국회의원 후보는 N명이다. 다솜이는 이 기계를 이용해서 그 마을의 주민 M명의 마음을 모두 읽었다.
다솜이는 기호 1번이다. 다솜이는 사람들의 마음을 읽어서 자신을 찍지 않으려는 사람을 돈으로 매수해서 국회의원에 당선이 되게 하려고 한다. 다른 모든 사람의 득표수 보다 많은 득표수를 가질 때, 그 사람이 국회의원에 당선된다.
예를 들어서, 마음을 읽은 결과 기호 1번이 5표, 기호 2번이 7표, 기호 3번이 7표 라고 한다면, 다솜이는 2번 후보를 찍으려고 하던 사람 1명과, 3번 후보를 찍으려고 하던 사람 1명을 돈으로 매수하면, 국회의원에 당선이 된다.
돈으로 매수한 사람은 반드시 다솜이를 찍는다고 가정한다.
다솜이가 매수해야하는 사람의 최솟값을 출력하는 프로그램을 작성하시오.
입력
첫째 줄에 후보의 수 N이 주어진다. 둘째 줄부터 차례대로 기호 1번을 찍으려고 하는 사람의 수, 기호 2번을 찍으려고 하는 수, 이렇게 총 N개의 줄에 걸쳐 입력이 들어온다. N은 50보다 작거나 같은 자연수이고, 득표수는 100보다 작거나 같은 자연수이다.
출력
첫째 줄에 다솜이가 매수해야 하는 사람의 최솟값을 출력한다.
예제 입력 1 복사
3 5 7 7
예제 출력 1 복사
2
예제 입력 2 복사
4 10 10 10 10
예제 출력 2 복사
1
예제 입력 3 복사
1 1
예제 출력 3 복사
0
예제 입력 4 복사
5 5 10 7 3 8
예제 출력 4 복사
4
출처
스스로 고민
- 힙에 대해서 격파한다는 생각으로 힙 문제를 집중적으로 풀어보려고 한다.
- 현재 문제를 보면 어떠한 상황에서의 최솟값을 만들어야한다.
- 최소힙?!
접근법
- 입력의 첫 번째 줄에는 후보의 수 N
- 두 번째 줄은 차례대로 기호 1번 , 찍을려고 하는 사람의 수
- 총 N 개의 후보 -> N <= 50
- 득표수 <= 100
힙 문제인데 막상 어떻게 힙을 적용시켜야 하는지 아이디어가 바로 안 떠오르는 걸 보면 확실히 힙에 대해서 더 공부를 해야겠다.
의사코드
- 우선 힙을 선언하고 힙 리스트를 만들어준다.
- 힙에 요소를 추가해준다.
- 최대 힙으로 쓰기 위해 음수로 계산한다.
- 0번 인덱스의 값이 최대 힙이 될 때까지 이 전의 최대힙에서 1씩 뽑아서 0번 인덱스 값에 더해준다.
구현 코드
import heapq
people, *candidates = list(map(int, input().split()))
def answer(candidates):
num = 0
data =[]
for i in candidates:
data.append((num, i))
num+=1
max_heap = []
for item in data:
heapq.heappush(max_heap, (-item[1], item[0]))
count = 0
replace_list = []
while max_heap[0][1] != 0:
max_value = max_heap[0]
heapq.heappop(max_heap)
heapq.heappush(max_heap, (max_value[0]+1, max_value[1]))
count += 1
for i in max_heap:
if i[1] == 0:
replace_list.append((i[0]-1, i[1]))
else:
replace_list.append(i)
heapq.heapify(replace_list)
max_heap = replace_list
replace_list = []
return count
- 역시 틀렸다.
- 그래도 힙에 대해 좀 더 익숙해진거 같다.
- 실버 문제인데도 불구하고 이렇게 복잡하게 생각하는걸 보면 뭔가 내가 잘 사용을 하지 못 하고 있는 느낌이 들어서 다른 사람은 어떻게 사용했는지 봤다.
import heapq
if __name__ == "__main__":
n = int(input())
dasom = int(input())
heap = []
for _ in range(n - 1):
num = int(input())
heapq.heappush(heap, (-num, num))
if len(heap) == 0:
print(0)
else:
count = 0
while True:
now = heapq.heappop(heap)[1]
if now >= dasom:
count += 1
dasom += 1
heapq.heappush(heap, (-now + 1, now - 1))
else:
break
print(count)
- 코드를 보면서 input을 저렇게 필요할 때 순서대로 부르는 방법을 알게 되었다.
- 또한 해당 방법처럼 간단하게 힙을 사용할 수도 있었다.
- 내가 제일 해맸던 부분은 최대 힙에 대해 업데이트하고 다솜 데이터를 따로 뽑아서 데이터를 조작하고 다시 힙을 만드는 생각을 했다.
- 생각해보면 위와 같이 힙을 만든 후 최대 힙에서 두 번째 요소만 계속 업데이트 하고 힙이 아닌 다솜 데이터만 따로 분리해서 업데이트해서 비교를 계속하면 쉽게 해결할 수 있는 문제였다.
- 너무 어렵게 생각했고 경험이 없어서 문제를 제대로 풀지 못 한 케이스였다.
시간 복잡도와 공간 복잡도
- 시간 복잡도:
- 힙에 요소를 추가하는 데 시간복잡도는 O(log N)이며, N-1번 반복
- 힙에서 요소를 추출하고 다시 추가하는 과정도 O(log N)이며, 최악의 경우 N-1번 반복
- 따라서 전체 시간 복잡도는 O((N-1) * log N)
- 공간 복잡도:
heap
리스트에는 최대 N-1개의 요소가 저장- 다른 변수들의 메모리 사용은 상수이므로 무시
- 따라서 전체 공간 복잡도는 O(N)
회고 과정
- 파이썬으로 힙을 이용할 때는 heapq를 불러와서 하면 되는데 이 부분이 아직까지도 잘 안되는거 보면 확실하게 이해하지 못 했다.
- 힙 자료구조를 갖고 어떻게 활용 해야 하는지 감이 오지 않았다.
- 문제를 더 많이 풀어봐야 할 것 같다.
다른 사람 코드를 보고 그 기준으로 어떤 부분을 개선 할 수 있는지 최종 회고
n = int(input())
me, *data = [int(input()) for _ in range(n)] + [0]
cnt = 0
while me <= max(data):
M = max(data)
idx = data.index(M)
data[idx] -= 1
me += 1
cnt += 1
print(cnt)
- 시간 복잡도:
data
리스트에서 최대값을 찾는 데 시간복잡도는 O(N)- 최대값의 인덱스를 찾는 데도 O(N) 시간이 걸릴 수 있다.
while
루프는me
가data
의 최대값 이상이 될 때까지 실행되므로, 최악의 경우data
의 길이 N만큼 반복된다..- 따라서 전체 시간 복잡도는 O(N^2)다.
- 공간 복잡도:
- 입력으로 주어진 데이터를 저장하는데 사용되는 메모리의 양은 O(N)
- 그 외에
n
,me
,cnt
,M
,idx
와 같은 상수 개수의 변수가 있으므로 추가적인 공간 복잡도는 O(1) - 따라서 전체 공간 복잡도는 O(N)
느낀점
- 해당 문제를 보면 max로 푸는 사람들이 많았는데 max 혹은 min 값으로 문제를 풀 수 있다고 생각이 들 경우 힙을 이용해서 문제를 풀어보는 습관을 가져보도록 해야겠다.
댓글남기기