Algorithm

[분할정복] 카라츠바 알고리즘

devidea 2014. 6. 21. 00:51

두 n자리 수의 곱셉은 n2 의 연산이 필요하다. 

1의 자리부터 n자리까지 연속에서 곱을해서 더했던 방법이 우리들에게 익숙하다. 


 왼쪽과 같이 2자리수의 곱을 계산했을 때 22의 곱의 합으로 이루어짐을 볼수 있다.


카라츠바 알고리즘은 두 수 x,y의 곱을 x,y의 절반인 수들의 곱 3번과 덧셈으로 계산하는 방식이다.

큰 문제를 반으로 분할해 가며 해결하는 방법으로 분할정복의 대표 알고리즘이다.


아래는 wikipedia에 나온 알고리즘 단계의 내용이다.

http://en.wikipedia.org/wiki/Karatsuba_algorithm


[카라츠바 알고리즘 정리]

x와 y를 B진법의 n자리수라고 했을 때, n보다 작은 양수 m에 대해 x,y를 쪼갤 수 있다.


x = x1Bm + x0
y = y1Bm + y0

(단, x0과 y0는 Bm보다 작다.)

z2 = x1y1
z1 = x1y0 + x0y1
z0 = x0y0

라고 할 때, x와 y의 곱은

xy = (x1Bm + x0)(y1Bm + y0)
z2 B2m + z1 Bm + z0

이 식은 4번의 곱셉을 해야하는데, 카라츠바 알고리즘으로 xy를 3번의 곱셉으로 구할 수 있음을 증명한다.


z2 = x1y1 라 하자.
z0 = x0y0 라 하자.
z1 = (x1y1 + x1y0 + x0y1 + x0y0) - x1y1 - x0y0 =x1y0 + x0y1

이므로

z1 = (x1 + x0)(y1 + y0) − z2  z0 라 할 수 있다.


알고스팟에서 카라츠바 알고리즘을 활용한 문제가 있다.

https://algospot.com/judge/problem/read/FANMEETING


해당 문제의 정답은 카라츠바 알고리즘을 통해 풀 수 있다. 알고리즘은 아래 url에서 확인했다.

http://jmk.pe.kr/pages/read/logs/library/karatsuba-fast-integer-multiplication


아래 소스에서 참고점은 자리수 올림 부분을 주석처리했다.

#include<iostream>
#include<vector>
#include<algorithm>
#include<string>
using namespace std;

void normalize(vector<int>& num) {
     num.push_back(0);
     // 자릿수 올림을 처리한다
     for (int i = 0; i < num.size(); i++) {
          if (num[i] < 0) {
               int borrow = (abs(num[i]) + 9) / 10;
               num[i + 1] -= borrow;
               num[i] += borrow * 10;
          }
          else if (num[i] > 0) {
               num[i + 1] += num[i] / 10;
               num[i] %= 10;
          }
     }
     if (num.back() == 0) num.pop_back();
}

vector<int> multiply(const vector<int>& a, const vector<int>& b) {
     vector<int> c(a.size() + b.size() + 1, 0);
     for (int i = 0; i < a.size(); i++)
          for (int j = 0; j < b.size(); j++)
               c[i + j] += a[i] * b[j];
     /*normalize(c);*/
     return c;
}

void addTo(vector<int>& a, const vector<int>& b, int k) {
     a.resize(max(a.size(), b.size() + k));
     for (int i = 0; i < b.size(); i++) a[i + k] += b[i];
     /*normalize(a);*/
}

void subFrom(vector<int>& a, const vector<int>& b) {
     a.resize(max(a.size(), b.size()) + 1);
     for (int i = 0; i < b.size(); i++) a[i] -= b[i];
     /*normalize(a);*/
}

vector<int> karatsuba(const vector<int>& a, const vector<int>& b) {
     int an = a.size(), bn = b.size();
     // a 가 b 보다 짧을 경우 둘을 바꾼다
     if (an < bn) return karatsuba(b, a);
     // 기저 사례: a 나 b 가 비어 있는 경우
     if (an == 0 || bn == 0) return vector<int>();
     // 기저 사례: a 가 비교적 짧은 경우 O(n^2) 곱셈으로 변경한다.
     if (an <= 50) return multiply(a, b);

     int half = an / 2;
     // a 와 b 를 밑에서 half 자리와 나머지로 분리한다
     vector<int> a0(a.begin(), a.begin() + half);
     vector<int> a1(a.begin() + half, a.end());
     vector<int> b0(b.begin(), b.begin() + min<int>(b.size(), half));
     vector<int> b1(b.begin() + min<int>(b.size(), half), b.end());
     // z2 = a1 * b1
     vector<int> z2 = karatsuba(a1, b1);
     // z0 = a0 * b0
     vector<int> z0 = karatsuba(a0, b0);
     // a0 = a0 + a1; b0 = b0 + b1
     addTo(a0, a1, 0); addTo(b0, b1, 0);
     // z1 = (a0 * b0) - z0 - z2;
     vector<int> z1 = karatsuba(a0, b0);
     subFrom(z1, z0);
     subFrom(z1, z2);
     // ret = z0 + z1 * 10^half + z2 * 10^(half*2)
     vector<int> ret;
     addTo(ret, z0, 0);
     addTo(ret, z1, half);
     addTo(ret, z2, half + half);
     return ret;
}

string toString(vector<int> a) {
     string ret;
     while (a.size() > 1 && a.back() == 0) a.pop_back();
     for (int i = 0; i < a.size(); i++)
          ret += char('0' + a[a.size() - 1 - i]);
     return ret;
}

vector<int> fromString(const string& s) {
     vector<int> ret;
     for (int i = 0; i < s.size(); i++)
          ret.push_back(s[i] - '0');
     reverse(ret.begin(), ret.end());
     return ret;
}

int hugs(const string& members, const string& fans){
     int N = members.size(), M = fans.size();
     vector<int> A(N), B(M);
     for (int i = 0; i < N; ++i) A[i] = (members[i] == 'M');
     for (int i = 0; i < M; ++i) B[M - i - 1] = (fans[i] == 'M');

     vector<int> C = karatsuba(A, B);
     int allHugs = 0;
     for (int i = N - 1; i < M; ++i){
          if (C[i] == 0){
               ++allHugs;
          }
     }
     return allHugs;
}

vector<int> result;
string a, b;

int main()
{
     int c;
     cin >> c;

     while (c-- > 0)
     {
          cin >> a;
          cin >> b;
          result.push_back(hugs(a, b));
     }

     for (int i = 0; i < result.size(); i++){
          cout << result[i] << endl;
     }
}


반응형