Back to : algorithms

Solved AC Platinum 1
SAC Class 7 Essential

Contents

풀이

수열과 쿼리 5 링크 와 거의 같은데, 조금 덜 자명한 문제이다.

먼저, 수열과 쿼리 5를 어떻게 푸는지 모른다면 이 글 을 권한다. Mo’s Algorithm이라는 마법의 알고리즘이 있어서, 쿼리당 $O(N)$ 이 걸리는 구간 쿼리를 오프라인에 처리함으로써 쿼리당 평균 $O(\sqrt{n})$ 비슷한 시간으로 줄여낼 수 있다. sqrt decomposition의 일종인데, 위 링크의 글이 너무 잘 쓰여 있다.

수열과 쿼리 5와는 달리, 수쿼6은 실제로 각 element가 몇개인지를 명확히 알아야 하고, 그과정에서 0인지 아닌지만 세면 되는 수쿼5와는 달리 뭔가 더 복잡한 문제가 된다.

첫번째 방법은, $x$번 나온 수가 몇 개 있는지를 세는 배열 c[x]를 정의하고, c[x] 가 nonzero인 마지막 element를 기록하는 변수를 만들어서 이를 잘 트래킹하는 것이다. 별로 어렵지는 않지만 조금 귀찮고, 이 풀이는 justicehui님의 풀이 에 잘 설명되어 있다.

두번째 방법은, 자료구조를 추가로 쓰는 것이다. 조금 느리긴 하지만, 만약 set을 쓴다고 가정하면, {i, x}i가 x번 나왔다 라는 의미로 사용함으로써, $O(\log n)$ 시간에 최댓값을 찾을 수 있고, $O(\log n)$ 시간에 값을 변경할 수 있다. 다만, set을 쓰면 시간이 매우 빡빡하고 (되는지는 안 내 봤는데, 안 되지 않을까? MO의 구현체가 나보다 훨씬 빠르다면 가능할 수도 있다) 임의의 원소를 변경할 수 있는 힙을 쓰면 시간내에 구겨 들어 간다. 나는 700ms 정도 걸렸는데, $O(n \sqrt n \log n)$ 정도 복잡도는 2020년에는 $n = 1e5$면 돌아갈만 하다 :) 그러나 STL의 priority queue는 이런 연산을 지원하지 않으므로, 임의의 원소를 $O(\log n)$에 변경 가능하고 최댓값을 $O(1)$에 반환하는 heap을 직접 구현해야 한다. 어쩌다보니 같은 분이 작성하신 secmem의 이 글 에 구현된 구현체 정도면 충분하다.

코드

#include <bits/stdc++.h>
#pragma GCC optimize("O3")
#pragma GCC optimize("Ofast")
#pragma GCC target("avx,avx2,fma")
#define ll long long
#define int ll
#define eps 1e-7
#define all(x) ((x).begin()),((x).end())
#define usecppio ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
using namespace std;
using pii = pair<int, int>;

struct Elem
{
    int i, w;
    bool operator<(const Elem &o) const {
        return w == o.w ? i < o.i : w > o.w;
    }
};

struct PQ
{
    Elem *arr;
    int *pos;
    int sz;

    PQ(int mx) : sz(0)
    {
        arr = new Elem[mx + 1];
        pos = new int[mx + 1];
    }
    
    void push(int i, int w)
    {
        ++sz;
        arr[sz] = { i, w };
        pos[i] = sz;
        up(sz);
    }

    void change(int i, int w, bool delta = false)
    {
        int cur = pos[i];
        int k = arr[cur].w;
        int nw;
        if (delta)
            arr[cur].w += w;
        else
            arr[cur].w = w;
        nw = arr[cur].w;
        if (k > nw)
            down(cur);
        else
            up(cur);
    }

    void up(int cur)
    {
        while (cur > 1)
        {
            if (arr[cur].w <= arr[cur >> 1].w)
                break;
            swap(arr[cur], arr[cur >> 1]);
            pos[arr[cur].i] = cur;
            cur >>= 1;
        }
        pos[arr[cur].i] = cur;
    }

    void down(int cur)
    {
        while ((cur << 1) <= sz)
        {
            int mx;
            if ((cur << 1) == sz || (arr[cur << 1].w > arr[(cur << 1) + 1].w))
                mx = (cur << 1);
            else
                mx = (cur << 1) + 1;
            if (arr[cur].w >= arr[mx].w)
                break;
            swap(arr[cur], arr[mx]);
            pos[arr[cur].i] = cur;
            cur = mx;
        }
        pos[arr[cur].i] = cur;
    }

    int pop()
    {
        int ret = arr[1].i;
        arr[1] = arr[sz--];
        pos[arr[1].i] = 1;
        down(1);
        return ret;
    }

    int peek()
    {
        return arr[1].w;
    }

    void del(int i)
    {
        int cur = pos[i];
        int k = arr[cur].w;
        arr[cur] = arr[sz--];
        pos[arr[cur].i] = cur;
        if (arr[cur].w > k)
            down(cur);
        else
            up(cur);
    }
};
PQ pq(101010);
int BLK = 1;
int N, M, cur;
struct query
{
    int idx, l, r, v, ans;
    bool operator < (query &o)
    {
        if (r/BLK != o.r/BLK)
            return r / BLK < o.r/BLK;
        return l < o.l;
    }
};

struct Mo
{
    vector <int> seq;
    vector <query> Q;

    void solve()
    {
        sort(all(Q));
        int lo = 1, hi = 0;
        for (query &q : Q)
        {
            while (q.l < lo) add(--lo, q);
            while (q.r > hi) add(++hi, q);
            while (q.l > lo) sub(lo++, q);
            while (q.r < hi) sub(hi--, q);
            get_ans(q);
        }
        sort(all(Q), [](query &a, query &b) -> bool{
            return a.idx < b.idx;
        });
    }

    void add(int idx, query &q)
    {
        pq.change(seq[idx], +1, true);
    }

    void sub(int idx, query &q)
    {
        pq.change(seq[idx], -1, true);
    }

    void get_ans(query &q)
    {
        q.ans = pq.peek();
    }
};
vector <int> v;
Mo MO;
int32_t main()
{
    usecppio
    cin >> N;
    if (N <= 100) BLK = 1;
    else
        BLK = sqrt(N);
    MO.seq = vector<int>(N + 5, 0);
    for (int i = 1; i <= N; i++)
    {
        cin >> MO.seq[i];
        v.push_back(MO.seq[i]);
    }
    sort(all(v)); v.erase(unique(all(v)), v.end());
    for (int i : v)
        pq.push(i, 0);
    cin >> M;
    for (int i = 1; i <= M; i++)
    {
        int l, r; cin >> l >> r;
        MO.Q.push_back({i, l, r, 0, 0});
    }
    MO.solve();
    for (auto q : MO.Q)
        cout << q.ans << '\n';
}