Featured image of post [Algorithm] C++ 백준 15517번: Array Manipulation at Moloco (Hard)

[Algorithm] C++ 백준 15517번: Array Manipulation at Moloco (Hard)

서로 다른 정수 배열에서 각 원소보다 작은 이전 원소의 개수를 좌표 압축과 Fenwick Tree로 누적해 O(N log N)에 계산한다. N=1e6을 위한 빠른 입출력까지 함께 정리한다.

서로 다른 정수 배열 \(A\)가 주어질 때,

\[ S[i] = |\{ j \mid 1 \le j < i \ \wedge\ A[j] < A[i] \}| \]

를 모든 \(i\)에 대해 출력하는 문제다.
\(n\)이 최대 \(10^6\)이라서 \(O(n^2)\) 비교는 불가능하고, 좌표 압축 + Fenwick Tree로 \(O(n\log n)\)에 해결한다.

문제 정보

문제 링크: https://www.acmicpc.net/problem/15517

문제 요약:

  • 길이 \(n\)의 서로 다른 정수 배열 \(A\)가 주어진다.
  • 각 \(i\)에 대해, \(i\)보다 앞에 있으면서 \(A[i]\)보다 작은 원소의 개수 \(S[i]\)를 출력한다.

제한 조건:

  • 시간 제한: 2초
  • 메모리 제한: 512MB
  • \(1 \le n \le 1{,}000{,}000\)
  • \(|A[i]| \le 2{,}000{,}000{,}000\)
  • \(A\)는 distinct

입출력 예제

입력 1:

1
2
3
4
5
6
5
10
5
12
1
11

출력 1:

1
2
3
4
5
0
0
2
0
3

접근 방식

핵심 관찰: “이전 원소 중 작은 것의 개수”는 ‘순위 누적’으로 계산된다

왼쪽부터 \(A[i]\)를 하나씩 처리한다고 하자.

  • 지금까지 본 값들이 Fenwick Tree에 “등장 횟수(=1)”로 들어있다면,
  • \(A[i]\)보다 작은 값들의 총 등장 횟수가 곧 \(S[i]\)가 된다.

즉,

  • ans = sum(rank(A[i]) - 1)
  • add(rank(A[i]), +1)

만 반복하면 된다.

좌표 압축이 필요한 이유

\(A[i]\) 범위가 \(\pm 2 \times 10^9\)이므로 그대로 인덱스로 Fenwick Tree를 만들 수 없다.
따라서 값들을 정렬해서 \(1 \ldots m\) 범위의 순위(rank) 로 바꾼다.

알고리즘 설계 (Mermaid Flowchart)

flowchart TD
    A["입력: n, 배열 A[1..n]"] --> B["정렬용 배열 sortedA에 A 복사"]
    B --> C["sortedA 정렬 후 중복 제거(안전)"]
    C --> D["Fenwick Tree 크기 = |sortedA| 준비"]
    D --> E["i=1..n 순회"]
    E --> F["idx = lower_bound(sortedA, A[i]) + 1"]
    F --> G["S[i] = fw.sumPrefix(idx-1)"]
    G --> H["fw.add(idx, 1)"]
    H --> I["S[i] 출력 버퍼에 추가"]
    I --> J{"다음 i 존재?"}
    J -- "yes" --> E
    J -- "no" --> K["버퍼 전체 출력"]

복잡도 분석

항목복잡도비고
시간 복잡도\(O(n \log n)\)정렬 \(O(n \log n)\) + 각 원소마다 Fenwick 2회
공간 복잡도\(O(n)\)입력 배열 + 정렬 배열 + Fenwick

코너 케이스 및 실수 포인트

케이스설명처리 방법
n=1이전 원소가 없음항상 0 출력
음수/큰 값값 범위가 매우 큼좌표 압축 필수
distinct문제에서 보장그래도 unique로 안전 처리 가능
대용량 출력최대 \(10^6\) 줄문자열 버퍼에 모아 fwrite 권장

구현 코드 (C++)

 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
80
81
82
83
84
85
86
87
88
89
90
// 42jerrykim.github.io에서 더 많은 정보를 확인 할 수 있다
#include <bits/stdc++.h>
using namespace std;

struct FastInput {
    static constexpr size_t BUFSIZE = 1 << 20;
    char buf[BUFSIZE];
    size_t idx = 0, size = 0;

    inline char readChar() {
        if (idx >= size) {
            size = fread(buf, 1, BUFSIZE, stdin);
            idx = 0;
            if (size == 0) return 0;
        }
        return buf[idx++];
    }

    template <typename T>
    bool readInt(T &out) {
        char c;
        do {
            c = readChar();
            if (!c) return false;
        } while (c <= ' ');

        bool neg = false;
        if (c == '-') { neg = true; c = readChar(); }

        long long val = 0;
        while (c > ' ') {
            val = val * 10 + (c - '0');
            c = readChar();
        }
        out = neg ? -val : val;
        return true;
    }
};

struct Fenwick {
    int n;
    vector<int> bit;
    Fenwick(int n = 0) : n(n), bit(n + 1, 0) {}

    inline void add(int i, int v) {
        for (; i <= n; i += i & -i) bit[i] += v;
    }
    inline int sumPrefix(int i) const {
        int s = 0;
        for (; i > 0; i -= i & -i) s += bit[i];
        return s;
    }
};

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    FastInput in;
    int n;
    if (!in.readInt(n)) return 0;

    vector<long long> A(n), sortedA;
    sortedA.reserve(n);

    for (int i = 0; i < n; i++) {
        in.readInt(A[i]);
        sortedA.push_back(A[i]);
    }

    sort(sortedA.begin(), sortedA.end());
    sortedA.erase(unique(sortedA.begin(), sortedA.end()), sortedA.end());

    Fenwick fw((int)sortedA.size());

    string out;
    out.reserve((size_t)n * 3);

    for (int i = 0; i < n; i++) {
        int idx = (int)(lower_bound(sortedA.begin(), sortedA.end(), A[i]) - sortedA.begin()) + 1;
        int ans = fw.sumPrefix(idx - 1);
        fw.add(idx, 1);

        out.append(to_string(ans));
        out.push_back('\n');
    }

    fwrite(out.c_str(), 1, out.size(), stdout);
    return 0;
}

참고 문헌 및 출처