KoreanFoodie's Study
[종만북 문제] 팬미팅 (문제 ID : FANMEETING, 난이도 : 상) 본문
Data Structures, Algorithm/종만북
[종만북 문제] 팬미팅 (문제 ID : FANMEETING, 난이도 : 상)
GoldGiver 2024. 3. 6. 20:26
프로그래밍 대회에서 배우는 알고리즘 문제해결 전략(이하 종만북)에서 소개된 문제를 풀이합니다. 알고리즘에 진심이시라면, 직접 구매하셔서 읽어보시는 것을 추천합니다!
핵심 :
1. 무식하게 풀면, 시간 복잡도가 O(N^2) 가 되어 시간초과가 발생한다.
2. 카라츠바의 알고리즘을 사용해 보자!
[종만북 문제] 팬미팅 (문제 ID : FANMEETING, 난이도 : 상)
팬미팅 문제는 종만북에서 처음으로 나오는 '상' 난이도의 문제이다.
하지만 걱정하지 마라. 손은 눈보다 빠르니까...
근데 사실 손이 빠른 건 별로 도움이 안된다.
어쨌든, 이 문제는 '무식하게' 푼다고 하면 O(N^2) 로 풀어볼 수도 있을 것이다. 예시 코드는 아래와 같다(시간초과 함에 유의).
#include <iostream>
#include "stdlib.h"
#include <cmath>
#include <vector>
#include <unordered_set>
using namespace std;
/****************************************************************************************************/
/****************************************************************************************************/
/****************************************************************************************************/
int M;
int N;
vector<char> members;
vector<char> fans;
bool shake(char m, char f)
{
if (m == 'M' && f == 'M')
return true;
return false;
}
void sol()
{
// 각 멤버별로 팬과 비교를 한다고 할때,
// validOffset 에 해당 항목이 없으면 다음 차시에는 Skip 할 수 있다.
// 즉, validOffset 의 각 항목은 '번째' 를 의미한다
unordered_set<int> validOffset;
for (int i = 0; i <= N - M; ++i)
validOffset.insert(i);
for (int i = 0; i < M; ++i)
{
vector<int> shakes;
for (int offset : validOffset)
{
if (shake(members[i], fans[i + offset]))
shakes.push_back(offset);
}
for (int shk : shakes)
validOffset.erase(shk);
}
cout << validOffset.size() << endl;
}
void inputHandler()
{
string member;
string fan;
cin >> member;
cin >> fan;
M = member.size();
N = fan.size();
members.resize(M);
fans.resize(N);
for (int i = 0; i < M; ++i)
members[i] = member[i];
for (int i = 0; i < N; ++i)
fans[i] = fan[i];
}
int main()
{
int cases;
cin >> cases;
while (cases--)
{
inputHandler();
sol();
}
return 0;
}
나름대로 최적화를 한다고 해 봐도, 결국 최악의 경우에는 O(N^2) 이 그대로 나오게 된다...
그렇다면 이 문제를 어떻게 효율적으로 해결할 수 있을까?
이 문제는 사실 아래처럼, 멤버와 팬의 곱셈으로 이해할 수도 있다.
A 를 멤버, B 를 팬, C 를 멤버와 팬이 포옹할지 말지에 대한 값이라고 하면 이해가 될 것이다.
남자일 때의 값을 1, 여자일 때의 값을 0 이라고 하면... 둘의 곱셈이 0 이 될 때에만 포옹을 한다고 이해할 수 있다!
즉, 우리는 이 어마무시한 길이의 곱셈을 카라츠바 알고리즘을 이용해서 풀면, 시간초과를 회피할 수 있다.
그런데...
카라츠바 알고리즘은 또 뭘까? 위키피디아를 보면, 그 내용이 명확히 나와 있다.
대충 효율성은 아래와 같다. 귀찮아서 대충 때우는 게 아니다. 🤥
자, 그럼 카라츠바 알고리즘을 이용해서 이 문제를 풀어보자. 자세한 설명은 주석으로 달아 두었다. 😄
#include <iostream>
#include "stdlib.h"
#include <cmath>
#include <vector>
#include <unordered_set>
using namespace std;
/****************************************************************************************************/
/****************************************************************************************************/
/****************************************************************************************************/
// a += b * (10^k) 를 구현. 자릿수는 처음이 제일 작은 자리수
void addTo(vector<int>& a, const vector<int>& b, int k)
{
int an = a.size(), bn = b.size();
a.resize(max(an, bn + k));
for (int i = 0; i < bn; ++i)
a[i + k] += b[i];
}
// a -= b 를 구현. a >= b 를 가정
void subFrom(vector<int>& a, const vector<int>& b)
{
int an = a.size(), bn = b.size();
a.resize(max(an, bn + 1));
for (int i = 0; i < bn; ++i)
a[i] -= b[i];
}
vector<int> multiply(const vector<int>& a, const vector<int>& b)
{
int an = a.size(), bn = b.size();
vector<int> ret(an + bn + 1, 0);
for (int i = 0; i < an; ++i)
for (int j = 0; j < bn; ++j)
ret[i + j] += (a[i] * b[j]);
return ret;
}
// 두 긴 정수의 곱을 반환
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(bn, half));
vector<int> b1(b.begin() + min(bn, 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;
}
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');
}
int ret = 0;
vector<int> C = karatsuba(B, A);
for (int i = N - 1; i < M; i++)
if (C[i] == 0)
ret++;
return ret;
}
int main()
{
int cases;
cin >> cases;
while (cases--)
{
string members, fans;
cin >> members >> fans;
cout << hugs(members, fans) << endl;
}
return 0;
}
'Data Structures, Algorithm > 종만북' 카테고리의 다른 글
[종만북 문제] 와일드카드 (문제 ID : WILDCARD, 난이도 : 중) (0) | 2024.03.08 |
---|---|
[종만북 문제] 외발 뛰기 (문제 ID : JUMPGAME, 난이도 : 하) (0) | 2024.03.08 |
[종만북 문제] 울타리 잘라내기 (문제 ID : FENCE, 난이도 : 중) (1) | 2024.03.05 |
[종만북 문제] 쿼드 트리 뒤집기 (문제 ID : QUADTREE, 난이도 : 하) (0) | 2024.03.05 |
[종만북 문제] Synchronizing Clocks, 시계 맞추기 (문제 ID : CLOCKSYNC, 난이도 : 중) (0) | 2024.03.04 |
Comments