백준 2109. 순회강연
저명한 학자에게 여러 대학에서 강연 요청이 들어 왔다.
각 대학에서는 몇일 안에 와서 강연을 해주면 얼마의 강연비를 주겠다고 제시했다.
학자가 하루에 한 곳에서만 강연을 할 수 있으므로,
최대한 유리하게 스케줄을 짜서, 가장 많은 돈을 벌 수 있도록 순회 강연을 하려고 한다.
이 학자가 최대 벌 수 있는 강연비는 총 얼마인가?
문제에서 제시한 조건
- n개의 대학에서 d일 안에 와서 강연을 해주면 p만큼의 강연료를 지불하겠다고 알려준다.
- 4개의 대학에서 제시한 p값이 각각 50, 10, 20, 30이고, d 값이 차례로 2, 1, 2, 1이라고 하자. 이럴 대에서는 첫째 날에 4번 대학에서 강연을 하고, 둘째 날에 1번 대학에서 강연을 하면 80만큼의 돈을 벌 수 있다.
코드 리뷰
총 두가지 방식으로 문제를 풀었다. 시도는 방식2를 먼저 해보았으나 시간 초과로 실패하였다.
방식1로 문제를 푸는데는 성공했으나 시간을 꽤 많이 소비했다. 엄청 짧은 코드로 짧은 시간에 푼 사람도 있었다.
실패한 코드는 나중에 다시 보고 리뷰하고자 올려두었다.
방식1. 제시된 기한별로 몇개의 선택지가 있는지 알아내고 (d가 1000인 옵션이 100개이고, 그 다음 d가 1이라면, d가 1000인 옵션은 모두 수용이 가능하다.) 수용가능한 경우 다 더해주고 아닌 경우에만 정렬해 계산하는 방법. 기한 옵션의 수만큼 while문을 돌린다.
문제 풀이 방법
제시된 기한별로 몇개의 옵션이 있는지, 몇개의 기한이 있는지 집합에 넣어서 알아낸 후 정렬된 옵션리스트에서 선택가능한 옵션들이 어느 index까지인지 계산하여 fee를 더해주고 그렇지 않은 경우에는 기한을 변경하여 다시 계산하여 문제를 해결한다.
수도 코드
파이썬 코드
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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | import sys def greedy(feeList, dCountList, dSet): total = 0 feeList = sorted(feeList, key=lambda fee:fee[0], reverse=True) feeList = sorted(feeList, key=lambda fee: fee[1], reverse=True) while dSet: first_d = dSet.pop(0) # 첫번째 기한 값 next_d = 0 # 기한 옵션이 하나라면 0 이어야 1일을 포함하여 가능 횟수를 셀 수 있음 if len(dSet) > 0: next_d = dSet[0] period = first_d - next_d # 강연갈 수 있는 일 수 dCount = dCountList[str(first_d)] # 해당 기한으로 요청받은 강연 수 # 기한이 넉넉하다면 모든 옵션들을 강연할 수 있다. if period >= dCount: for i in range(dCountList[str(first_d)]): option = feeList.pop(0) total += option[0] # fee 합산 else: # dCount만큼 fee 기준으로 정렬한다. (같은 기한이므로 기한 정렬은 패쓰~) selected_options = sorted(feeList[:dCount], key=lambda option:option[0], reverse=True) extra_options = [] # 기한이 부족할 경우 가능한 옵션 수만큼만 강연가고 나머지만 sort for i in range(dCount): if i < period: # 기한 내 option = selected_options[i] total += option[0] # fee 합산 else: if next_d == 0: break else: extra_options.append((selected_options[i][0], next_d)) # 초과하는 옵션들은 다음 기한으로 변경 if next_d != 0: dCountList[str(next_d)] += len(extra_options) # 해당 기한의 옵션들의 수를 더 추가해준다. feeList = extra_options + feeList[dCount:] return total if __name__ == "__main__": n = int(sys.stdin.readline()) feeList = [] dCountList = {} # 기한이 같은 옵션들의 수를 체크한다. dSet = set() # 제시된 기한들의 집합 값 for i in range(n): p, d = map(int, sys.stdin.readline().strip().split()) if str(d) in dCountList: dCountList[str(d)] += 1 else: dSet.add(d) dCountList[str(d)] = 1 feeList.append((p,d)) dSet = sorted(dSet, reverse=True) print(greedy(feeList, dCountList, dSet)) | cs |
실패한 코드 설명 - 남은 선택지의 기한을 1만큼 줄여나가기 (시간 초과)
그러나 이 문제에서 해결해야 하는 문제상황은 제시한 기한이 동일하고 그 안에서 유리한 옵션들을 선택해 나가야 할때이다.
이 문제를 쉽게 해결하기 위해서 기한이 먼 옵션부터 차례로 선택해 나간다.
동일한 기한(d)이 제시된 옵션이 있다면, 강연비(p)가 높은 옵션 하나를 선택하고 남은 선택지의 기한을 1만큼 줄여준다.
그리고 줄여진 기한과 동일한 옵션이 있다면 강연비(p)가 높은 옵션을 선택하면 된다.
그러고 남은 옵션들은 또다시 기한을 1만큼 줄여준다. 동일한 기한이 제시된 옵션이 없다면
하나 남은 옵션을 선택해주고 그 다음 기한을 가진 옵션들을 찾아서 또다시 비교해주면 된다.
이 작업을 기한이 1일일때까지 (정확히 말하면 제시한 d가 젤 적은 옵션까지 조건을 비교했을 때까지) 반복하면 된다.
하지만..결과적으로 시간초과가 났다. d의 값이 큰 옵션이 여러개일 경우에는 정렬이 매우자주 일어나기 때문이다.
수도 코드 1 - 남은 선택지의 기한을 1만큼 줄여나가기 (시간 초과)
# 고려해야 하는 모든 옵션이 사라진다면 선택을 멈춘다.
total_fee = 0
sort optionList by p # 강연비를 기준으로 정렬
sort optionList by d in descending order # 기한을 기준으로 정렬 (python은 기존 정렬 순서를 최대한 유지하면서 다른 조건으로 정렬한다.)
while optionList:
# optionList는 기한별로 강연비가 가장 큰 옵션 순서로 정렬되어 있다.
pop optionList[0] up and add total_fee
if d is 1, break # 1일차에 갈 강연을 선택했으면 더 이상 다른 강연을 정렬할 필요가 없다.
All remaining options are deducted by 1 by each option.
Arrange options with same d (d is the reduced values)
return total_fee
파이썬 코드 1 - 남은 선택지의 기한을 1만큼 줄여나가기 (시간 초과)
시간초과가 뜨는 경우
수도코드의 알고리즘대로 다으음의 코드를 짤 경우에는 시간초과가 뜬다.
10 100000
10 100000
10 100000
10 100000
10 100000
... d가 100000일 경우의 옵션이 100000개있을 경우
아래의 코드에서는 불필요한 정렬이 매번 일어난다.
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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | import sys def greedy(options): total_fee = 0 # option은 항상 기한이 넉넉한 옵션 중에서 강연비가 높은 기준으로 정렬 while options: selected = options.pop(0) total_fee += selected[0] # 선택한 강연료 합산 if selected[1] == 1: # d가 1이면 더 이상 다른 옵션을 선택할 필요가 없음 break same_d_index = -1 for i in range(len(options)): # 이미 선택한 옵션의 기한과 같다면 기한을 1만큼 차감 if selected[1] == options[i][1]: options[i] = (options[i][0], options[i][1] - 1) same_d_index = i # 1만큼 차감한 같은 경우에는 sort를 위해서 어디까지 같은 기한인지 i를 계산한다 elif selected[1] - 1 == options[i][1]: same_d_index = i continue else: break # 정렬해야 하는 부분이 2개 옵션 이상 있으면 정렬 if same_d_index > 0: # 필요한 부부분만 재정렬 same_d_options = options[:same_d_index + 1] sorted_options = sorted(same_d_options, key=lambda option: option[0], reverse=True) options = sorted_options + options[same_d_index + 1:] return total_fee if __name__ == "__main__": n = int(sys.stdin.readline()) feeList = [] for i in range(n): p, d = map(int, sys.stdin.readline().strip().split()) feeList.append((p,d)) feeList = sorted(feeList, key=lambda fee: fee[0], reverse=True) feeList = sorted(feeList, key=lambda fee: fee[1], reverse=True) print(greedy(feeList)) | cs |