https://www.acmicpc.net/problem/1150
전개
언뜻 보면 가장 짧은 거리만 취하면 되는 그리디로 보인다. 하지만 문제의 예제에서는 가장 짧은 경우인 회사 2 - 회사 3을 선택하고 있지 않다.
만약 {2, 3}를 택하게 되면 {1, 2}와 {3, 4}를 쓰지 못하게 되고 여기서 괜찮은 경우가 있을 수 있으므로 완벽한 최적해를 찾을 수 없다.
이를 거꾸로 보면 {2, 3}을 포기하면 {1, 2}와 {3, 4}를 챙길 수 있다는 말이 된다.
우리는 여기서 다음과 같은 전략을 생각해볼 수 있다.
일단 값이 작은 쌍을 가져가되, 상황에 따라 해당 쌍을 버리고 옆의 두 쌍을 취하기
그러면 예제를 완벽히 설명할 수 있다.
1. 일단 {2, 3}을 가져간다. 그러면 한 쌍이 연결된다.
2. {2, 3}을 버리고 {1, 2}, {3, 4}를 가져가는 경우와 나머지 연결되지 않은 쌍을 살펴본다. 이때 {1, 2}, {3, 4}를 취하는 쪽이 제일 작다.
3. {2, 3}을 버리고 {1, 2}, {3, 4}를 가져간다. 이러면 두 쌍이 연결되고 최적해 4가 된다.
이런 과정을 통해 k 쌍을 가져가도록 하면 된다.
유효성
만약 원래 있던 쌍을 버리는 행동을 계속할 경우 중간의 쌍들이 계속 버려지고 옆으로만 가는 일이 생기지 않냐는 의문이 생길 수 있지만 문제 조건에서 k는 n / 2 이하라는 조건이 있다. 이는 원래 쌍을 버리는 행위는 계속 반복될 수 없음을 의미한다.(해당 쌍을 버리는 것보다 다른 쌍을 가져가는 게 나은 경우를 고려하면 이해할 수 있다.)
구현
i와 i - 1 사이의 거리를 저장하도록 한다. 추가로 out of bound를 방지하기 위해 {0, 1}과 {n, n + 1}의 거리를 inf값으로 둔다.
그리고 우리는 이를 min heap으로 관리하면서 k 쌍을 확보할 것이다.
그리고 옆의 쌍을 가져가는 행동을 구현하기 위해 현재 회사 i에 대해 점거되지 않고 제일 가까운 왼쪽 회사의 위치와 점거되지 않고 제일 가까운 오른쪽 회사의 위치를 배열 $L$과 $R$로 관리할 것이다.
위들을 통해 min heap에는 다음과 같은 정보가 담긴다.
연결된 회사 덩어리들의 중심 회사 위치와 그들을 연결하는 값
min heap으로 값을 꺼내면서 해당 회사가 점거된 경우 건너뛰고 점거되지 않았으면 해당 가중치를 더한다.
그리고 해당 쌍보다 양 옆 쌍을 먹는 경우가 최적인 경우를 고려하여 양 옆 쌍을 먹는 가중치를 다시 계산하여 가중치 배열에 반영하도록 한 뒤 해상 회사가 점거되었음을(힙에 들어갈 것이므로) 표시한다.
그러면 $L$과 $R$을 갱신해야 한다. i 기준으로는 옆으로 한칸씩 더 밀어야 하고 이때 밀린 인덱스에 대해 $L[R[i]] = i, R[L[i]] = i$처럼 다시 다른 쪽에 가까운 회사를 i로 지정하도록 한다.
왜냐하면 이때 한 번 더 갱신하면 다시 i를 점거하는 것이기 때문이다. 이 이유로 인해 i는 지금 당장 표시하지 않는 것이다.
이는 포함 배제 원리로 설명할 수도 있는데 조악한 그림으로는 다음과 같이 표현할 수 있다.
그림에 따라 포함 배제 연산은 최대 2번 시행될 수 있다. 물론 앞서 언급한 유효성 문단에서 여기서 더 확장하는 일은 일어나지 않는다. 그럴 바에야 이미 새로운 쌍을 가져가게 되기 때문
아무튼 이렇게 최적의 k 쌍을 선택하면 구현이 끝나게 된다.
전체 코드
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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
|
#pragma GCC target("sse,sse2,sse3,ssse3,sse4,avx,avx2,fma")
#pragma GCC optimize("Ofast")
#pragma GCC optimize("unroll-loops")
#include "bits/stdc++.h"
#include "ext/rope"
using namespace std;
using namespace __gnu_cxx;
using ll = long long;
using pii = pair<int, int>;
int n, k;
bitset<100002> dominated;
int ar[100001];
int l[100001];
int r[100001];
int dl[100002];
priority_queue<pii, vector<pii>, greater<>> pq;
void solve()
{
cin >> n >> k;
for (int i = 1; i <= n; i++)
{
cin >> ar[i];
l[i] = i - 1;
r[i] = i + 1;
if (i > 1)
{
dl[i] = ar[i] - ar[i - 1];
pq.push({dl[i], i});
}
}
l[n + 1] = n;
dl[1] = dl[n + 1] = 1e9;
pq.push({dl[1], 1});
pq.push({dl[n + 1], n + 1});
int ans = 0;
int cnt = 0;
while (cnt < k)
{
auto [fee, i] = pq.top();
pq.pop();
if (dominated[i]) continue;
++cnt;
ans += fee;
dl[i] = dl[r[i]] + dl[l[i]] - dl[i];
pq.push({dl[i], i});
dominated.set(l[i]);
dominated.set(r[i]);
r[i] = r[r[i]];
l[i] = l[l[i]];
r[l[i]] = l[r[i]] = i;
}
cout << ans;
}
int main()
{
#ifdef LOCAL
freopen("input.txt", "r", stdin);
// freopen("output.txt", "w", stdout);
#endif
cin.tie(nullptr);
ios::sync_with_stdio(false);
solve();
}
|
cs |
제출 기록
여담
탐욕적 방법의 정당성 증명은 MCMF로 한다고 한다.
'백준 > 그리디' 카테고리의 다른 글
백준 16225 - 제271회 웰노운컵 (0) | 2022.07.06 |
---|---|
백준 1437 - 수 분해 (0) | 2022.07.05 |
백준 20370 - 공정한 게임 (0) | 2022.06.17 |
백준 23354 - 돌 가져가기 (0) | 2022.06.03 |
백준 2777 - 숫자 놀이 (0) | 2022.05.13 |