두 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;
}
}
반응형
'Algorithm' 카테고리의 다른 글
| AVL Tree (2) - 삭제 (0) | 2017.08.25 |
|---|---|
| AVL Tree (1) - 개념, 삽입 (0) | 2017.08.24 |
| [algospot] 조세푸스 문제 (연결리스트) (0) | 2017.08.03 |
| [Euler] Highly divisible triangular number (0) | 2016.03.25 |
| 분할정복 (0) | 2014.04.04 |