-
imos-Hanbyeol Trick and its applications알고리즘 2023. 6. 11. 15:55
(2023.06.11 online IHT with linear memory 문단 추가)
이 글에서는 imos법을 확장한 imos-Hanbyeol Trick (IHT)의 개념과 응용에 대해 설명한다.
귀여운 imos쨩의 그림이다. imos법
imos법이란 구간 덧셈 업데이트를 빠르게 처리하는 테크닉이다. 다음과 같은 문제를 생각해보자.
0으로 초기화된
크기의 차원 배열 가 주어진다. 다음 연산을 개 처리한 후 배열을 출력하여라. : 에 를 더한다. (앞으로 모든 : notation은 닫힌 구간이다.)나이브하게 했을 때 시간복잡도는 최악의 경우
이지만, imos법을 사용하면 에 처리할 수 있다. 이에 대한 자료는 인터넷에 많이 있기 때문에 자세히 설명하지는 않겠다. 요약하면 에 2차원 누적합을 적용했을 때 답이 나오도록 적절히 값을 더하고 빼면 번의 덧셈으로 업데이트에 대한 정보를 모두 저장할 수 있다.imos법의 강력한 점은 단순히 1차원, 2차원 뿐만 아니라 임의의 차원에 적용할 수 있다는 점이다. imos-Hanbyeol Trick은 이 사실을 최대한 활용하는 트릭이다.
더 귀여운 imos쨩 그림이다. imos-Hanbyeol Trick
다음과 같은 문제를 생각해보자.
0으로 초기화된
크기의 차원 배열 가 주어진다. 다음 연산을 개 처리한 후 배열을 출력하여라. : 에 를 더한다.이 문제 역시 나이브하게 하면
이지만 imos법을 잘 활용하면 에 풀 수 있다. 이를 imos-Hanbyeol Trick이라고 정의한다.imos-Hanbyeol Trick을 이용한 풀이는 다음과 같다. 편의상 배열의 인덱스를 비트마스크로 관리하고 있다는 점에 유의하여라.
vector<int> imos_hanbyeol_trick(int N, int Q, vector<pair<int, int>> query){ vector<int> A(1<<N); for (int i=0;i<Q;i++){ A[query[i].first] += query[i].second; // imos법 처럼 값을 더해준다. } for (int i=0;i<N;i++){ for (int j=0;j<(1<<N);j++) if (j&(1<<i)){ A[j] += A[j^(1<<i)] // imos-hanbyeol trick } } return A; }
코드를 보면 뭔가 우리가 알던 imos법과 다르다는 느낌을 많이 받을 것이다. 코드가 정확히 어떻게 작동하는지 알아보자.
놀란 한별이 이중반복문을 돌기 전의 배열
를 라고 하자. 루프를 돌고 나면 , 이 된다. ( 는 비트스트링이다.) 루프를 돌고 나면 , , , 이 된다.귀납적으로,
번째 루프까지 돌고 나면 에는 의 하위 개 비트가 submask고 상위비트는 모두 일치하는 모든 마스크에 대해, 해당되는 배열의 값을 더한 값이 저장됨을 알 수 있다. 식으로 적으면 다음과 같다.식 정리를 해보면 식이 잘 맞아떨어진다는 것을 확인할 수 있다. 따라서,
루프까지 돌고 나면, 에는 다음 값이 저장되어있다. 에 해당하는 업데이트는 을 submask로 가지는 모든 에 를 더하는 업데이트이므로, 최종 상태의 는 구하고자하는 와 동일하다.놀라운 사실은, 우리가 지금까지 계산한 것이 imos법과 정확히 동일하다는 사실이다! 상상하기 힘들지만,
개의 축을 가진 차원 공간의 초직육면체를 생각해보자. ( 정도로 놓고 8개의 값이 어떻게 변하는지 생각해보면 좀 더 편하다.) 번째 루프를 도는 행위는 imos법에서 번째 축으로 스위핑하면서 누적합을 해주는 것과 동일하다는 것을 알 수 있다. 즉, imos-Hanbyeol Trick은 차원 imos법을 비트마스킹을 이용해서 간단하게 구현한 것이다.imos-Hanbyeol Trick을 다르게 표현하면 배열
가 주어질 때 다음 배열 를 빠르게 계산하는 것이다.havana723님이 그리신 귀여운 imos쨩과 한별이의 그림이다. Online imos-Hanbyeol Trick
다음과 같은 점화식의 dp를 생각해보자.
는 상수시간에 계산할 수 있는 함수라고 가정하자. imos-Hanbyeol Trick은 쿼리/값을 모두 받고 한번에 처리하기 때문에 이처럼 online 꼴의 점화식을 계산할 수 없다는 한계점을 가지고 있다. 그러나, imos-Hanbyeol Trick을 돌릴 때 나오는 값들을 잘 활용하면 이를 계산하는 것이 가능하다.imos-Hanbyeol Trick을 증명할 때 사용한 식을 가져와보자. 앞에서 설명했듯이, 하위
개 비트에 대한 submask들의 합이다.이를 이용하면
를 다음과 같이 적을 수 있다.따라서,
에 대한 정보가 있다면 를 에 구할 수 있다.한편,
의 정의를 잘 생각해보면 에 대한 점화식도 쉽게 구할 수 있다.편의상
로 정의하면 편하다.이를 코드로 구현하면 다음과 같다. 시간복잡도는
이다.vector<int> online_imos_hanbyeol_trick(int N){ vector<int> dp(1<<N, 0); vector<vector<int>> S(1<<N, vector<int>(N, 0)); for (int msk=0;msk<(1<<N);msk++){ for (int i=0;i<N;i++) if (msk&(1<<i)){ dp[msk] += S[msk^(1<<i)][i]; } dp[msk] = f(dp[msk]); for (int i=0;i<N;i++){ if (i) S[msk][i] = S[msk][i-1]; else S[msk][i] = dp[msk]; if (msk&(1<<i)) S[msk][i] += S[msk^(1<<i)][i]; } } return dp; }
Online imos-Hanbyeol Trick with linear memory
이 파트는 sait2000님의 도움을 받아 작성되었습니다.
웰노운 아니야 Online imos-Hanbyeol Trick을 나이브하게 짜면 공간복잡도
이 되지만, 값 갱신을 적절하게 해주면 공간복잡도 으로 짤 수 있다! 메모리를 대략 /20 해주기 때문에 메모리를 매우 아낄 수 있고, 시간도 꽤 줄어드는 꽤나 강력한 최적화라고 볼 수 있다. 일단 코드부터 보자.vector<int> online_imos_hanbyeol_trick_linear_memory(int N){ vector<int> dp(1<<N, 0); vector<int> S(1<<N, 0); for (int msk=0;msk<(1<<N);msk++){ for (int i=0;i<N;i++) if (msk&(1<<i)){ dp[msk] += S[msk^(1<<i)]; } dp[msk] = f(dp[msk]); S[msk] = dp[msk]; for (int i=0;msk&(1<<i);i++){ int r = msk + 1; int l = r - (1<<i); for (int msk2=l;msk2<r;msk2++){ S[msk2] += S[msk2 ^ (1<<i)]; } } } return dp; }
편의상 아까 정의했던
를 에 대한 크기 의 subset sum이라고 부르자. 현재 for문에서 돌고 있는 에 대해, (단, )라고 하자. 즉, 는 의 비트 중 켜져있는 비트들이다. 이때, 에 저장된 값은 다음과 같다. : 크기 의 subset sum : 크기 의 subset sum...
: 크기 의 subset sum따라서, 현재 상태에서
가 성립하게 되고, 점화식이 잘 계산된다.식만 보면 이해하기 힘드니 예시를 들어보자. 현재
라고 하면, 배열에는 다음과 같은 값들이 저장되어있다. (편의상 인덱스를 다섯자리 이진수로 적었다.) : 크기 의 subset sum : 크기 의 subset sum : 크기 의 subset sum 은 다음과 같이 계산된다.이제, 남은 부분은
배열을 갱신하는 부분이다. for (int i=0;msk&(1<<i);i++)에 해당하는 반복문이 바로 그 파트이다. 배열은 인덱스 0부터 길이 , , ... , 의 "블록"을 이룬다는 점에 유의하자. 를 1 늘릴 때 이 블록들이 어떻게 갱신되어야하는지 살펴볼 것이다. 이면, 짜리 블록을 추가하면 갱신이 끝나기 때문에 따로 해줄 것이 없다. (S[msk] = dp[msk]) 이면, 기존에 있던 짜리 블록과 새로 추가된 짜리 블록을 합쳐서 짜리 블록으로 만들어줘야한다. 이때, 인덱스를 잘 생각해보면 앞에 있는 블록의 인덱스는 에 해당하는 비트가 꺼져있고, 뒤에 있는 블록의 인덱스는 에 해당하는 비트가 켜져있기 때문에, 뒤에 있는 블록의 값을 앞에 있는 블록의 값으로 갱신해주면 된다. 앞에 있는 블록은 비트가 꺼져있기 때문에 크기를 늘려도 subset sum이 변하지 않는다. (S[msk2] += S[msk2 ^ (1<<i)])이렇게 블록을 1번 합쳐주고나서 끝날 수도 있지만,
이라면 짜리 블록이 2개가 되기 때문에 이를 또 합쳐줘야한다. 이 과정은 여러 번 반복될 수 있다. 따라서, 블록을 합치는 과정을 를 0부터 늘리면서 계속하다가, 이 되는 순간 갱신을 하지 않고 중단해줘야 한다는 것을 알 수 있다.길이
짜리 블록 2개를 합치려고 한다면 앞에 있는 블록의 인덱스는 모두 에 해당하는 비트가 꺼져있고 뒤에 있는 블록은 모두 켜져있다는 사실을 이용해서 아까처럼 갱신해주면 된다. (S[msk2] += S[msk2 ^ (1<<i)])따라서, S를 갱신하는 부분만 코드로 적으면 다음과 같다.
S[msk] = dp[msk]; for (int i=0;msk&(1<<i);i++){ int r = msk + 1; int l = r - (1<<i); for (int msk2=l;msk2<r;msk2++){ S[msk2] += S[msk2 ^ (1<<i)]; } }
각 원소에 대해 저장된 subset sum의 크기가 최대
까지 갈 수 있고, 크기를 늘리는 것은 덧셈 0번 또는 1번에 되기 때문에 배열을 갱신하는 과정은 시간복잡도 임을 알 수 있다. 따라서, Online IHT를 시간복잡도 , 공간복잡도 에 할 수 있다.OR Convolution using imos-Hanbyeol Trick (a.k.a. imos-Hanbyeol Gwabegigul)
이 파트는 koosaga님의 글(https://codeforces.com/blog/entry/115438)을 참고하여 작성되었습니다.
imos-한별 꽈배기굴의 개형도이다. 배열
와 가 주어질 때, 와 의 OR Convolution 는 다음과 같다.나이브하게 계산하면
의 시간이 걸리지만, imos-Hanbyeol Trick을 이용해서 에 계산할 수 있다.imos-Hanbyeol Trick을 이용하면 다음을 구할 수 있다.
이때, 다음이 성립함을 알 수 있다.
즉,
은 에 imos-Hanbyeol Trick을 적용한 배열이다. 따라서, imos-Hanbyeol Trick을 거꾸로 해주면(Inverse imos-Hanbyeol Trick, IIHT) OR Convolution 결과를 얻을 수 있다.코드는 다음과 같다.
vector<int> imos_hanbyeol_gwabegigul(int N, vector<int> A, vector<int> B){ vector<int> C(A.size()); // imos-hanbyeol trick for (int i = 0; i < N; i++) { for (int j = 0; j < (1 << N); j++) { if (j & (1<<i)) { A[j] += A[j ^ (1 << i)]; B[j] += B[j ^ (1 << i)]; } } } for (int i = 0; i < (1 << N); i++) { C[i] = A[i] * B[i]; } // inverse imos-hanbyeol trick for (int i = N - 1; i >= 0; i--) { for (int j = (1 << N) - 1; j >= 0; j--) { if (j & (1<<i)) { C[j] -= C[j ^ (1 << i)]; } } } return C; }
연습문제
난이도 순서가 아닐 수 있다.
https://www.acmicpc.net/problem/2803
2803번: 내가 어렸을 때 가지고 놀던 장난감
어젯밤에 나는 어렸을 때 가지고 놀던 장난감 상자 N개를 창고에서 발견했다. 상자를 좀 뒤적여보니 나는 M종류의 장난감을 가지고 있었다. 오랫동안 기억 속에서 잊혀졌던 장난감을 보니, 내가
www.acmicpc.net
https://www.acmicpc.net/problem/25390
25390번: Traveling Junkman Problem
고물상이
개의 집을 순회하며 물건을 사고 판다. 각 집에는 번부터 번까지 번호가 붙어 있다. 고물상이 취급하는 물건은 총 종류가 있으며, 마찬가지로 번부터 번까지 번호가 붙어www.acmicpc.net
https://www.acmicpc.net/problem/27841(Online imos-Hanbyeol Trick으로 풀어보자.)
27841번: Problem Setting
Farmer John created
( ) problems. He then recruited ( ) test-solvers, each of which rated every problem as "easy" or "hard." His goal is now to create a problemset arranged in increasing order of difficulty, consisting owww.acmicpc.net
Marek and Matching (easy version)
'알고리즘' 카테고리의 다른 글
오일러 경로? (1) 2024.08.27 1^k + 2^k + ... + n^k를 O(k)에 구하기 (0) 2024.03.26 N번째 페리 수열의 K번째 항을 빠르게 구하기 (3) 2023.11.26 Semi-local string comparison (수열과 쿼리 42) (3) 2022.12.10 샤모스-호이 알고리즘 (Shamos-Hoey Algorithm) (3) 2021.09.22