BOJ8462) 배열의힘
먼저 이 문제를 살펴보자
* n개의 원소가 존재하고, [L,R] quer가 들어올 때 마다 부분 배열의 힘을 구하는 것이다.
* 여기서 Ks라 하면 자연수 s의 개수를 뜻하는데, 부분배열에서 이 모든 자연수 s에 대해 Ks * Ks * s 를 구하는 것이다.
=> 나이브하게 모든 query에 대하여 다 훑어본다면 O(N^2)의 시간복잡도를 갖는다.
이 때 Sqrt decompostion을 적용해보자.
Sqrt Decomposion이란?
* 간단하게 N개의 집합을 sqrt(N)개의 집합으로 나누어 값을 다룬다고 생각하면 되는데,
이 문제에서는 query 에 적용하는 Query Sqrt Decompostion을 사용할 것이다.
또한 query에 대하여 분할 하는 것을 Mo's algorithm이라고 한다,
각각 query[L,R]에 대해서 L/sqrt(N)으로 정렬한다. 만약에 값이 같은경우, R 값을 그다음 키값으로 정렬한다. (오름차순)
정렬을 하게 되면 query의 순서가 뒤바뀌기 때문에 따로 index값을 저장해주어야 한다.
값을 변경하는 query (update query) 가 존재하는경우 이 알고리즘을 적용할 수 없다.
대신, 구간의 특정 수의 개수를 구하는 거 같은 특수한 형태의 문제에는 아주 잘 적용된다.
결론적으로, N개의 원소가 존재하고 quert[L,R] 이 존재할 때, L/sqrt(N)과 R우선순위로 정렬 하게 된다면
theorem
1. N/sqrt(N) == sqrt(N)개의 집합이 형성된다.
2. 하나의 Block 내에서 query(L,R] -> [L',R']의 변화가 있다면,L'-L <= sqrt(N)이고 R <= R' 이다.
3. 동일 Block내에서 R의 변화는 최대 N까지이므로, 최종적으로 모든 query에서의 R은 O(N^3/2) 시간복잡도 내에 변화한다.
4. 한 Block내에서 L의 range는 sqrt(N)이므로 최종적으로 모든 query에 대해서 L은 O(Q*sqrt(N)) 시간복잡도내에서 변화한다.
5. 그렇기 때문에 Mo's algorithm은 O((N+Q)*sqrt(N)*F) 의 시간 복잡도를 갖는다. (F는 L,R의 변화할 때마다 값을 처리해주는 함수를 만들었을 때, 그 함수의 처리 시간이다.)
이렇게 window size 로 정렬을 해주고.
현재의 알고있는 l값과 r값을 이용하여 꿈틀꿈틀 조정하게 되는데, 이 꿈틀꿈틀의 시간복잡도가 O(n*sqrt(n)) 인 것이다.
그러므로 구간합 구하기, 최대값 구하기 같은 유형의 문제들을 굳이 segment tree나 fenwick tree를 구현하지 않아도, 최초의 [L,R]을 설정해놓고 정렬된 query를 훑으면서 [L,R] ->[L',R']로의 query range를 확장,축소 해나가면 해결이 가능하다.
8462배열의힘 solution code
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 | #include <cstdio> #include <algorithm> #include <vector> #include <cmath> using namespace std; typedef long long ll; ll h; struct dot{ ll l,r,id; bool operator < (const dot& A) const{ return l/h == A.l/h ? r < A.r : l/h < A.l<h; } }; dot query[101000]; ll res[101000]; ll cnt[101000]; ll move(ll flag,ll val){ cnt[val] += flag; return (2ll * flag *cnt[val] - 1)*val; } ll n,k,l,r; ll sum; int main(){ scanf("%lld %lld",&n,&k); h = sqrt(n); vector<ll> a(n+1,0); for(int i = 1; i <= n ; i++) scanf("%lld",&a[i]); for(int i = 0 ; i < k; i++){ scanf("%lld %lld",&query[i].l,&query[i].r); query[i].id = i; } sort(query,query+k); l = query[0].l; r = query[0].r; for(int i = l; i<= r; i++){ sum += move(1,a[i]); } res[query[0].id] = sum; for(int i = 1; i < k ; i++){ while(l > query[i].l) sum += move(1,a[--l]); while(l < query[i].l) sum += move(-1,a[l++]); while(r > query[i].r) sum += move(-1,a[r--]); while(r < query[i].r) sum += move(1,a[++r]); res[query[i].id] = sum; } for(int i = 0 ; i < k ; i++){ printf("%lld\n",res[i]); } } | cs |
'알고리즘' 카테고리의 다른 글
BOJ 6171) 땅따먹기 (0) | 2018.05.13 |
---|---|
DP최적화 - Convex Hull Trick (0) | 2018.05.06 |
문자열 해싱 (0) | 2018.03.31 |
Min Path Cover in DAG (0) | 2018.03.31 |
L-R flow : 하한이 있는 플로우 (0) | 2018.03.31 |