오랜만에 머리도 식힐(?) 겸 프로젝트 오일러를 하나 풀어봤다.
문제의 골자는 세제곱수 중에 순열을 이루는 것이 다섯 개 있는 것을 찾아내는 것.
이 문제는 사실 unsigned long long을 사용하면 그닥 어렵지 않게 만들 수 있다.
몇 가지 포인트만 잡으면 꽤 빠르게 동작하는 프로그램을 만들 수 있다.
1. 모든 자릿수의 모든 숫자를 대상으로 계산하면 너무 복잡해짐
2. 세제곱수의 자릿수를 결정한 뒤 그 범위에 해당되는 세제곱근들만 계산
#include <cstdio>
#include <vector>
#include <cmath>
using namespace std;
bool ArePermutated(unsigned long long b1, unsigned long long b2) {
int cnt[10];
memset(cnt, 0, sizeof(cnt));
while (b1) {
++cnt[b1 % 10];
b1 /= 10;
}
while (b2) {
--cnt[b2 % 10];
b2 /= 10;
}
for (int i = 0; i < 10; ++i) {
if (cnt[i]) { return false; }
}
return true;
}
int main()
{
unsigned digits = 8;
bool solutionfound = false;
while (!solutionfound) {
unsigned low = (unsigned)pow(10.0, (digits - 1) / 3.0);
unsigned high = (unsigned)pow(10.0, digits / 3.0);
vector<unsigned long long> bigints;
for (unsigned i = low; i <= high; ++i) {
bigints.push_back((unsigned long long)i * i * i);
}
vector<unsigned> result;
for (unsigned i = low; i < high; ++i) {
result.clear();
result.push_back(i);
for (unsigned j = i + 1; j <= high; ++j) {
if (ArePermutated(bigints[i - low], bigints[j - low])) {
result.push_back(j);
}
}
if (result.size() == 5) {
printf("result: %u, %u, %u, %u, %u\n", result[0], result[1], result[2], result[3], result[4]);
solutionfound = true;
printf("%u ^ 3 = %llu\n", result[0], (unsigned long long)i * i * i);
break;
}
}
++digits;
}
return 0;
}
그런데, 막상 해놓고 보니 조금은 더 빠르게 할 수 있는 방법들이 눈에 띈다.
더불어, 조건이 좀 더 복잡해져 훨씬 긴 자릿수가 요구되면 대응할 수 없다는 한계도 보이고.
방법은 간단(?)하다.
vector<char>로 별도의 BigInt 형을 구현하는 것.
순열 여부 판단도 이 쪽이 좀 더 빠르게 구현 가능하다.
#include <cstdio>
#include <vector>
#include <cmath>
using namespace std;
//vector<char> 형으로 big int 구현
//단, 0은 빈 vector로 표현
typedef vector<char> BigInt;
void BigLetPositive(BigInt& big, unsigned num) {
big.clear();
while (num) {
big.insert(big.begin(), (char)(num % 10));
num /= 10;
}
}
void BigPrintf(BigInt& big) {
if (big.empty()) {
printf("0");
}
else {
for (size_t i = 0; i < big.size(); ++i) {
printf("%d", big[i]);
}
}
}
void BigMultiply(BigInt& result, BigInt& a, BigInt& b) {
result.clear();
if (a.empty() || b.empty()) { return; }
size_t sa = a.size();
size_t sb = b.size();
size_t sr = sa + sb;
result.resize(sr, 0);
for (size_t i = 0; i < sa; ++i) {
for (size_t j = 0; j < sb; ++j) {
result[i + j + 1] += a[i] * b[j];
}
for (size_t j = sr - 1; j > 0; --j) {
if (result[j] > 9) {
result[j - 1] += (result[j] / 10);
result[j] %= 10;
}
}
}
if (!result[0]) {
result.erase(result.begin());
}
}
bool BigArePermutated(BigInt& b1, BigInt& b2) {
if (b1.size() != b2.size()) { return false; }
int cnt[10];
memset(cnt, 0, sizeof(cnt));
size_t s = b1.size();
for (size_t i = 0; i < s; ++i) {
++cnt[b1[i]];
--cnt[b2[i]];
}
for (int i = 0; i < 10; ++i) {
if (cnt[i]) { return false; }
}
return true;
}
void BigGetCube(BigInt& cube, unsigned num) {
BigInt big;
BigLetPositive(big, num);
BigInt big2;
BigMultiply(big2, big, big);
BigMultiply(cube, big2, big);
}
int main()
{
DWORD dw = GetTickCount();
unsigned digits = 8;
bool solutionfound = false;
while (!solutionfound) {
unsigned low = (unsigned)pow(10.0, (digits - 1) / 3.0);
unsigned high = (unsigned)pow(10.0, digits / 3.0);
vector<BigInt> bigints;
for (unsigned i = low; i <= high; ++i) {
BigInt t;
BigGetCube(t, i);
bigints.push_back(t);
}
vector<unsigned> result;
for (unsigned i = low; i < high; ++i) {
result.clear();
result.push_back(i);
for (unsigned j = i + 1; j <= high; ++j) {
if (BigArePermutated(bigints[i - low], bigints[j - low])) {
result.push_back(j);
}
}
if (result.size() == 5) {
printf("result: %u, %u, %u, %u, %u\n", result[0], result[1], result[2], result[3], result[4]);
solutionfound = true;
printf("%u ^ 3 = ", result[0]);
BigInt t;
BigGetCube(t, i);
BigPrintf(t);
printf("\n");
break;
}
}
++digits;
}
return 0;
}
이 코드를 돌려보면 위의 unsigned long long을 사용한 코드보다 10배 가까이 더 빠르게 동작하는 것을 볼 수 있다.
덧, unsigned long long 쪽은 64비트로 컴파일하면 32비트에 비해 세 배 이상 빠르게 동작한다. ㄷㄷㄷ
테일러 급수로 구현한 ln() 함수 (0) | 2020.03.22 |
---|---|
C/C++/C# 에서 간단하게 inf/NaN 확인하는 방법 (0) | 2020.02.07 |
RGB ↔ YCbCr 변환 수식 이야기 (1) | 2018.08.18 |
최신 CPU에선 scalar 연산이 SIMD보다 빠를 수도 있더라 (1) | 2018.05.11 |
심심해서 풀어본 "미래네 집 현관 비밀번호" (0) | 2016.12.04 |