본문 바로가기
BOJ

BOJ 11027

by gmroh 2025. 9. 4.

링크

 

5월에 gggkik와 랜덤으로 뽑은 문제를 풀다가 나온 문제이다. 장장 3일 동안 고민했을 만큼 매우 어려운 문제라고 생각한다. 여담으로 gggkik는 잠깐 고민하더니 모르겠다고 도망갔다.. 꽤 좋은 문제라고 생각해서 블로그 글을 작성하려고 했는데, 풀이를 설명할 엄두가 나지 않아서 이제서야 작성한다. 문제는 매우 간단하다. $swap$ 연산으로 선택 정렬을 구현한 의사 코드가 주어지고, $N$개의 수에 대해 $M$번째 원소까지 선택 정렬을 수행했을 때의 $swap$ 연산 횟수를 출력하면 된다. 

 

먼저, 몇 번의 시뮬레이션을 통해 어떤 규칙이나 패턴이 있는지 파악해 보자. 

$i = 1$일 때의 시뮬레이션
$i = 2$일 때의 시뮬레이션

 

어떤 $i$에 대한 연산이 일어나기 전 처음 수열과 마지막 수열을 비교해 보자. 

321 -> 132
432 -> 243

 

전체 수열을 $A$, 연산에 참여한 수로만 이루어진 $A$의 부분수열을 $S$라 하자. 한 번의 연산은 $S$를 1만큼 shift하는 것과 동치이다. 따라서, 모든 수는 자신의 차례가 오기 전에는 위치가 절대 앞으로 이동하지 않는다는 것을 알 수 있다. 또한, 선택 정렬의 성질로 인해 $S$는 $i$번째 칸의 수로 시작하는 strictly decreasing subsequence임을 알 수 있다. 

 

$S$에 속하는 이웃한 두 수 $u, v$가 있다고 하자. $S$의 성질로 인해 $u > v$이다. 이때, $A$에서 $u$와 $v$ 사이에는 $u$보다 작은 수가 존재하지 않는다. 만약 $u$와 $v$ 사이에 $u$보다 작은 수 $w$가 존재했다면, $u$와 이웃한 수는 $v$가 아니라 $w$가 되어야 하므로 불가능하다. 따라서 연산 과정에서 $v$가 정렬되기 이전에 $u$와 $v$의 순서는 유지된다. 즉, 어떤 수 $u$의 오른쪽에 있고 $u$보다 작은 모든 수는 정렬되기 전에는 $u$의 왼쪽으로 이동하는 것이 불가능하다.

 

위 관찰들을 토대로 $swap$이 일어나는 횟수를 더 큰 수의 관점에서 셀 것이다. $swap$ 연산이 일어난 횟수는 $\sum (\vert S \vert - 1)$이므로 어떤 인덱스 $x$에 대해 $A_y < A_x$인 $y$가 있으면, $y$가 정렬될 때 생성되는 배열인 $S_y$에 $x$가 포함되는 횟수의 합이 전체 답이 될 것이다.

 

$x$가 $S_y$에 포함되지 않는 경우에 대해 생각해 보자. $x$의 왼쪽에 $x$보다 작은 수가 존재하는 경우에 $x$는 $S_y$에 포함되지 않을 것이다. 이때, $x$보다 왼쪽에 있고 $x$보다 작은 정렬되지 않은 수들의 집합을 $L_x$이라 하자. 만약 $y$가 $x$의 왼쪽에 있었다고 생각해 보자. 정렬되지 않은 수들 중 $y$가 최솟값이므로 $S_y$에 $x$ 오른쪽에 있는 수가 들어가는 것은 불가능하다. 따라서 $\vert L_x \vert$가 1 감소한다. 이제 $y$가 $x$의 오른쪽에 있지만 $x$의 왼쪽에 $x$보다 작은 수가 존재해 $x$가 포함되지 않는 상황을 보자. 연산은 $S_y$를 한 번 shift하는 것과 동치이므로 $x$의 오른쪽에 있고 $x$보다 작은 수들 중 $y$가 빠져나가고 $x$보다 왼쪽에 있고 $x$보다 작은 수 하나가 $x$의 오른쪽으로 넘어올 것이다. 마찬가지로 $\vert L_x \vert$가 1 감소한다.

 

위 결과에 따라 $L_x$가 공집합이 되기 전에는 $x$가 연산에 절대 참여할 수 없으며, $x$보다 작은 수가 하나 정렬될 때마다 $\vert L_x \vert$가 1 감소한다는 것을 알 수 있다. 연산이 최대 $M$번 진행되므로 $x$가 $S_y$에 포함되는 횟수는 $max(0, min(\vert \{t | t < x\} \vert, M) - \vert L_{x0} \vert)$이다. 구현은 수를 오름차순으로 정렬하고 세그먼트 트리를 이용하면 어렵지 않다.

 

하지만 여기서 반례가 존재하는데, 중복하는 수들이 있는 경우이다. 만약 수열이 $ \{ 2, 2, 1\}$와 같다면, 같은 수들이 $swap$될 일은 없기 때문에 1을 정렬하는 과정에서 발생하는 답은 1이지만 위 풀이는 각각의 2에서 1번씩 세어 버려 2가 나오게 된다.

 

먼저 중복되는 수들의 성질을 살펴 보자. 당연하게도 중복되는 수들은 절대 $swap$되지 않는다. 앞선 상황과 같이 $S$에 속하는 이웃한 두 인덱스 $i, j$가 있다고 하자. 그리고 $A$에서 $A_k = A_i$인 $k$가 있다고 하자. $k < i$인 경우, $S$에 $i$ 대신 $k$가 들어가야 하므로 불가능하다. $i < k < j$인 경우, 연산 후의 순서는 $A_k, A_i, A_j$순으로 변경된다. $A_k = A_i$이고, $A$에서 $i$와 $k$ 사이에 $A_i$보다 작은 수가 있는 것은 불가능하므로 연산 전후 항상 $L_i = L_k$이다. 또한, $i$와 $k$ 사이에 $A_i$보다 작은 수가 추가되는 것은 불가능하므로 연산이 진행됨에 따라 $i$와 $k$의 위치가 번갈아가면서 바뀔 것이며, $L_i = L_k$는 유지될 것이다. 마지막으로 $j < k$인 경우, 연산 후 $A_i, A_k$의 순서는 변하지 않는다.

 

위 결과에 따라 같은 값을 갖는 인덱스 여러 개가 존재할 때, 계산에 반영되어야 하는 $L$은 항상 가장 앞에 있는 원소의 $L$이라는 것을 알 수 있다. 또한, 몇 번째 연산까지 진행되었는지에 관계 없이 가장 앞에 있는 수의 $L$은 초기 배열에서 가장 앞에 있었던 원소의 $L$과 같음을 알 수 있다. 따라서 구현할 때 초기 배열에서 가장 앞에 있는 수의 $\vert L \vert$을 이용해서 계산하면 된다. 이때, $L$ 내부에 중복 원소가 들어 있을 때의 계산은 기존과 동일하기 때문에 세그먼트 트리에 더해 줄 때는 모든 인덱스에 더해 주어야 한다. 

 

전체 코드와 테스트케이스 생성기, 도움이 되었던 테스트케이스들이다.

더보기
#include <bits/stdc++.h>
using namespace std;

using ll = long long;
using pll = pair<ll, ll>;

inline void init() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
}

class fenwick {
private:
    ll n;
    vector<ll> tree;
public:
    fenwick(ll n) : n(n), tree(n + 1) {}

    void update(ll idx, ll val) {
        for (idx++; idx <= n; idx += idx & -idx) tree[idx] += val;
    }

    ll sum(ll x) {
        ll ret = 0;
        for (x++; x > 0; x -= x & -x) ret += tree[x];
        return ret;
    }

    ll sum(ll s, ll e) {
        return sum(e - 1) - sum(s - 1);
    }
};

int main() {
    init();

    ll n, m;

    while (cin >> n >> m) {
        vector<ll> a(n);
        vector<pll> v(n);
        ll ans = 0;
        fenwick fw(n);

        for (ll i = 0; i < n; i++) {
            cin >> a[i];
            v[i] = {a[i], i};
        }

        sort(v.begin(), v.end());

        for (ll i = 0, e = 0; i < n; i = e) {
            auto [val, idx] = v[i];
            while (e < n and v[e].first == val) e++;
            ans += max(0LL, min(m, i) - fw.sum(0, idx));
            for (ll j = i; j < e; j++) {
                fw.update(v[j].second, 1);
            }
        }

        cout << ans << '\n';
    }

    return 0;
}
더보기
#include <bits/stdc++.h>
using namespace std;
using ll = long long;

int main() {
    int T = 5;

    std::mt19937_64 rng(std::random_device{}());
    auto rnd = [&](ll lo, ll hi) -> ll {
        return std::uniform_int_distribution<ll>(lo, hi)(rng);
    };

    for (ll tc = 0; tc < T; tc++) {
        ll n, m;
        vector<ll> a;

        n = rnd(1, 30);
        m = rnd(1, n);
        a.resize(n + 1);

        for (int i = 1; i <= n; i++) {
        	a[i] = rnd(-1, 100);
        }

        cout << n << ' ' << m << '\n';
        for (int i = 1; i <= n; i++) {
            cout << a[i] << ' ';
        }
        cout << '\n';
    }
    return 0;
}

 

3 3

27 13 14

9 1

25 14 27 18 10 19 3 18 14

6 5

19 20 20 10 27 9

4 3

7 26 6 2

10 1

15 18 2 17 18 30 0 8 2 28

 

2

3

6

5

2

 

16 15

5 48 29 93 26 51 35 39 16 42 17 99 59 98 78 87

3 1

46 53 4

27 15

36 78 72 9 50 34 9 18 92 90 57 80 4 58 43 100 51 23 77 33 78 9 37 10 42 42 2

11 9

30 48 38 51 34 36 72 0 2 59 43

2 1

61 38

22 11

81 21 94 50 47 69 9 89 69 99 77 71 99 69 67 3 32 91 60 89 87 5

20 4

54 86 95 72 38 15 65 7 1 -1 8 31 82 38 1 50 43 29 65 1

12 4

22 27 81 71 44 61 62 8 19 70 45 58

19 8

65 51 1 100 38 63 97 33 48 18 13 72 84 37 27 28 59 57 93

21 1

69 84 47 76 2 99 48 51 94 18 52 44 1 53 92 67 81 67 76 18 12

 

38

1

129

25

1

63

27

11

47

3

 

# 모두 다름

15 15

99 97 47 55 30 43 100 72 83 1 68 73 53 62 13

11 6

30 75 51 11 27 50 15 53 94 37 35

19 2

54 12 3 99 33 89 75 64 42 68 79 93 23 77 60 25 19 96 72

13 9

46 84 74 81 69 42 58 90 75 88 15 52 24

22 17

41 7 43 91 23 88 54 74 61 82 22 48 27 66 85 30 56 10 90 42 94 45

24 8

33 63 61 68 93 2 21 98 65 100 12 92 78 89 5 52 69 75 57 48 45 55 60 95

28 21

60 55 42 17 70 23 28 7 3 94 57 14 43 12 99 91 48 96 69 29 27 90 33 85 82 45 83 61

11 11

57 11 62 80 35 23 83 100 60 91 66

11 8

43 2 15 77 62 21 80 31 28 71 61

26 4

78 23 54 72 21 46 56 100 6 0 60 32 11 66 30 15 3 73 25 12 91 29 58 90 63 43

 

65

18

3

41

96

39

134

17

18

23

 

'BOJ' 카테고리의 다른 글

BOJ 14435  (2) 2025.09.05
BOJ 33601  (0) 2025.05.03
BOJ 17533  (0) 2025.05.03
BOJ 25055  (0) 2025.02.14
BOJ 16615  (0) 2025.02.13