ps:problems:boj:1851
추 정렬하기
ps | |
---|---|
링크 | acmicpc.net/… |
출처 | BOJ |
문제 번호 | 1851 |
문제명 | 추 정렬하기 |
레벨 | 다이아몬드 5 |
분류 |
그리디 |
시간복잡도 | O(n) |
인풋사이즈 | n<=100,000 |
사용한 언어 | Python |
제출기록 | 30864KB / 76ms |
최고기록 | 76ms |
해결날짜 | 2022/02/28 |
풀이
- 정렬했을때 각 추가 가야 할 위치들을 구해놓고 보면, 옮겨야 할 추들을 몇개의 사이클들로 분할할 수 있다.
- 크기 n짜리 사이클을 원래 위치로 돌리기 위해선 n-1번의 스왑이 필요하다. 아무 추 한개를 원래 자리로 옮기는 식으로 스왑하면, n-1번째에는 모두 제자리에 오게된다.
- 모든 추는 최소 한번씩은 위치를 옮겨야 하는데, 옮길때 항상 가장 무게가 작은 추와 함께 스왑되도록 하는 것이 최적 전략이 된다.
- 추의 무게가 2 4 5 3 1 순이라서 5짜리 사이클이 생겼다 치자, 가장 작은 무게인 1이, 5번 자리에 있으니까, 1-5를 스왑하고, 이제 1이 3번 자리에 갔으니 1-3을 스왑, 그리고 1-4, 1-2 순으로 스왑하면 모두 정렬된다.
- 이때의 총 비용은 sum(X) + min(X) * (len(X)-2) 로 쉽게 구해진다
- 하지만 이렇게 쉽게 생각하고 구현해서 제출했더니, 틀렸습니다를 받았다
- 질문 게시판에서 다음의 반례를 찾았다
- 1 1004 1001 1002 1003 의 경우, 답이 5016이 된다는 것.
- 저것을 5016으로 만들기 위해서는 1004 1001 1002 1003의 사이클을 n-1 번에 정렬하는 것이 아니라, 1을 사이클의 숫자와 스왑하고, 사이클을 정렬한 다음 다시 1을 사이클 밖으로 옮겨야 한다.
- 1 1004 1001 1002 1003 를 1001과 1을 스왑해서, 1001 1004 1 1002 1003 으로 바꾸고, 위에서 했던 방법대로 1001 1 1002 1003 1004 형태로 변환한 다음, 마지막에 다시 1001과 1을 스왑하는 것이다.
- 이러면 사이클에서 가장 작은 값과 전체에서 가장 작은 값(m)을 먼저 스왑하고, 윗 방법을 쓰고, 다시 스왑을 하니까, 총 비용이 (min(X)+m) + (sum(X)-min(X) + m*(len(X)-1) + (min(X)+m) = sum(X) + min(X) + m*(len(X)+1) 이 된다
- 이제 이 두 값중에서 작은 값을 고르도록 하면 된다.
- 시간 복잡도는 O(n).
코드
"""Solution code for "BOJ 1851. 추 정렬하기".
- Problem link: https://www.acmicpc.net/problem/1851
- Solution link: http://www.teferi.net/ps/problems/boj/1851
Tags: [Greedy]
"""
import sys
def main():
n = int(sys.stdin.readline())
w = [int(sys.stdin.readline()) for x in range(n)]
min_w = min(w)
orders = sorted(range(n), key=w.__getitem__)
is_visited = [False] * n
answer = 0
for i in range(n):
if is_visited[i]:
continue
weights = []
cur = i
while not is_visited[cur]:
is_visited[cur] = True
weights.append(w[cur])
cur = orders[cur]
answer += min(
sum(weights) + min(weights) * (len(weights) - 2),
sum(weights) + min(weights) + min_w * (len(weights) + 1))
print(answer)
if __name__ == '__main__':
main()
ps/problems/boj/1851.txt · 마지막으로 수정됨: 2022/03/04 16:26 저자 teferi
토론