티스토리 뷰

이 글에서는 BOJ 수열과 쿼리 5 문제를 해결하기 위한 Mo's Algorithm 에 대해서 설명하겠습니다.

 

Mo's Algorithm 은 update가 주어지지 않는 쿼리에서 적용할 수 있는 알고리즘으로,

가끔 세그먼트 트리로는 해결하지 못하는 문제를 해결할 수 있는 알고리즘입니다.

 

Mo's Algorithm 의 기본 아이디어는 쿼리의 순서를 원하는 대로 바꿀 수 있다는 것입니다.

update가 주어지지 않는 쿼리이므로, 원하는 순서로 배치하여 처리하고, 다시 순서를 바꾸어 출력하면 됩니다.

 

BOJ 수열과 쿼리 5 문제를 봅시다.

이 문제에서 이러한 아이디어를 생각해 봅시다.

 

cnt[x] = 지금까지 센 x의 개수, L[x]는 x번째 쿼리의 첫 번째 수, R[x]는 x번째 쿼리의 두 번째 수 일때

각 쿼리를 계산할 때 L[i] 에서 L[i+1]까지, R[i]에서 R[i+1]까지 보면서 cnt[x]를 처리해 주고, cnt[x]가 0에서 1로 바뀌면 ans++, 1에서 0으로 바뀌면 ans--를 해 줍니다.

 

이러한 처리를 통해 ans는 각 쿼리의 답이 됨을 알 수 있습니다.

하지만 시간복잡도는 O(n^2)이기 때문에 TLE가 나게 됩니다.

 

이 때 쿼리의 순서를 적절히 바꿔 줌으로써 시간복잡도를 줄일 수 있는데,

각 단계마다 L[i]에서 L[i+1]까지, R[i]에서 R[i+1]까지 움직이는 횟수를 줄여줄 수 있다면 될 것입니다.

 

Mo's Algorithm 을 이용하여 시간복잡도를 O(n*sqrt(n))으로 줄일 수 있습니다.

그 과정에 대한 직관적인 이해를 한 후에 스스로 코딩하는 것을 추천합니다.

 

이 코드에서 중요한 점은 쿼리의 순서를 바꾸는 과정에서 {L[i]/sqrtN, R[i]} 를 기준으로 정렬을 해주면

L[i], R[i]가 움직이는 횟수가 sqrtN*N이하로 줄어들 수 있다는 것고, 그것을 이용해 시간 복잡도를 줄인 것입니다.

#include<bits/stdc++.h>
using namespace std;
int N, M, L[100001], R[100001], sqrtN, cnt[1000001], ans[100001], A[1000001], ret;
vector<pair<pair<int, int>, int> > v;


int main(){
    scanf("%d", &N);
    for(int i = 1;i <= N;i++){
        scanf("%d", &A[i]);
    }
    sqrtN = sqrt(N);
    scanf("%d", &M);
    for(int i = 0;i < M;i++){
        scanf("%d %d", &L[i], &R[i]);
        v.push_back({{L[i]/sqrtN, R[i]}, i});
    }
    sort(v.begin(), v.end());
    for(int i = L[v[0].second];i <= v[0].first.second;i++) {cnt[A[i]]++; if(cnt[A[i]] == 1)ret++;}
    ans[v[0].second] = ret;
    for(int i = 1;i < M;i++){
        if(L[v[i-1].second] < L[v[i].second]){
            for(int j = L[v[i-1].second];j < L[v[i].second];j++){
                cnt[A[j]]--;
                if(cnt[A[j]] == 0) ret--;
            }
        }
        if(L[v[i-1].second] > L[v[i].second]){
            for(int j = L[v[i].second];j < L[v[i-1].second];j++){
                cnt[A[j]]++;
                if(cnt[A[j]] == 1) ret++;
            }
        }
        if(v[i-1].first.second < v[i].first.second){
            for(int j = v[i-1].first.second+1;j <= v[i].first.second;j++){
                cnt[A[j]]++;
                if(cnt[A[j]] == 1) ret++;
            }
        }
        if(v[i-1].first.second > v[i].first.second){
            for(int j = v[i].first.second+1;j <= v[i-1].first.second;j++){
                cnt[A[j]]--;
                if(cnt[A[j]] == 0) ret--;
            }
        }
        ans[v[i].second] = ret;
    }
    for(int i = 0;i < M;i++) printf("%d\n", ans[i]);
    return 0;
}

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2025/06   »
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
글 보관함