0. 발아점 (재활용)
이전 포스트 임의의 숫자가 제곱수인지 빠르게 판별하는 법에 이어지는 포스트임.
1. 문제에 대한 나의 접근(실패)
\(a^2+b^2+4a^2b^2 = c^2\)
을 아래와 같이 변형한 뒤
\(a^2+b^2+2ab+4a^2b^2-2ab = c^2\)
아래와 같이 정리했다.
\((a+b)^2+2ab(2ab-1) = c^2\)
여기서 모든 제곱수(\(c^2\))에 대해 이러한 관계를 만족시키는 a, b를 찾는 거다.
하지만, a, b는 Brute-Force하게 루프를 돌려야 되는데, 효율성이 낮다.
(c가 100일 때 돌렸던 루프를 c가 1000일 때도 또 돌려야 함)
따라서 실패.
2. 치욱님 솔루션
https://twitter.com/chiw00k/status/217625138395480064
a, b의 최대값은 \(\sqrt {20} \cdot 10^9\)이다.
여기서, a를 1~최대값, b를 1~i[각주:1] 까지 이중 루프를 돌린다.
이 때 \(a^2+b^2+4a^2 b^2\)을 계산해서 그 결과가 제곱수인가 판별하면 된다.
3. knauer0x 님의 홀수 배제 아이디어
https://twitter.com/knauer0x/status/217643729593442304
이전 포스트에서 언급했던 내용인데, 제곱수를 4로 나눈 나머지는 반드시 0 또는 1이다.
그런데, a, b가 모두 홀수라면 좌변의 결과를 4로 나눈 나머지는 반드시 2가 된다.
\(a^2+b^2+4a^2b^2\)
따라서, 루프를 돌릴 때 a, b가 모두 홀수인 경우는 배제하고 돌려도 된다.
4. Zaeku 님의 최적화
대규모의 Brute-Force 연산에서 연산시간을 줄이려면 물론 루프 자체의 숫자를 줄여야 된다.
그런데, 그것 만큼이나 중요한 것이 반복되는 연산의 연산횟수를 줄이는 것이다.
(이전 포스트 역시 그런 의미에서 쓴 것이다)
b의 루프에서 계속 반복적으로 사용되지만, a 루프에서 미리 계산 가능한 값이 둘 있다.
\(a^2\)과 \(4a^2\)이다.
이 두 값을 미리 계산해두면 b 루프에선 곱하기를 단 두 번만 할 수 있다.
그리고, 이전 포스트에서 언급한 제곱수 판별을 사용하면 연산 자체의 수를 최소화 할 수 있다.
5. 기타 최적화
a, b 루프에서 계속 \(a^2\), \(b^2\)을 계산하는데, 일정하게 증가하는 값이므로 더하기로 대체할 수 있다.
즉, \((a+1)^2-a^2 = 2a+1\), \((a+2)^2-a^2 = 4a+4\)를 사용하는 것이다.
더하기와 곱하기의 클럭 차이가 있기 때문에 조금이나마 시간 절감 효과를 볼 수 있다.
6. 결과
아래는 1~\(10^6\) 범위의 c에 대해 해를 찾으면서 위의 최적화를 모두 반영한 코드이다.
#include "stdafx.h"
#include <math.h>
#include <Windows.h>
#define BASENUM 64
bool bBase[BASENUM] = {false,};
void prepareToCheckSquare() {
for (int i=0; i<BASENUM; i++) {
bBase[i*i % BASENUM] = true;
}
}
inline bool IsSquare(unsigned long long int &num, unsigned long long int &ret) {
if (bBase[(int)(num & (BASENUM-1))]) {
ret = (unsigned int)(sqrtl((long double)num)+0.5);
return (ret*ret == num);
} else return false;
}
int _tmain(int argc, _TCHAR* argv[])
{
prepareToCheckSquare();
DWORD dw = GetTickCount();
unsigned long long int limit = 1000000LL;
unsigned long long int limit1 = (unsigned long long int)(sqrt(20.0)*limit/10);
unsigned long long int limit2 = limit*limit;
unsigned long long int i, j, area;
unsigned long long int isq, isq4, jsq, areasq;
int count=0;
for (i=1, isq=1, isq4=4; i<limit1; isq+=((i<<1)+1), isq4 = isq<<2, i++) {
if (i & 1) {
for (j=2, jsq=4; j<i; jsq+=((j<<2)+4), j+=2) {
areasq = isq + jsq + isq4*jsq;
if (areasq > limit2) break;
if (IsSquare(areasq, area)) {
printf("#%d. a=%llu, b=%llu, c=%llu\n", ++count, i, j, area);
}
}
} else {
for (j=1, jsq=1; j<i; jsq+=((j<<1)+1), j++) {
areasq = isq + jsq + isq4*jsq;
if (areasq > limit2) break;
if (IsSquare(areasq, area)) {
printf("#%d. a=%llu, b=%llu, c=%llu\n", ++count, i, j, area);
}
}
}
}
dw = GetTickCount()-dw;
printf("%u ms (%u sec)\n", dw, dw/1000);
return 0;
}
위에 언급한 최적화를 모두 적용했으며, 수 초 이내에 답을 출력해준다.
네이버에 올라온 모 회사 입사 테스트 문제 풀이 3/4 (0) | 2014.10.12 |
---|---|
네이버에 올라온 모 회사 입사 테스트 문제 풀이 2/4 (2) | 2014.10.12 |
네이버에 올라온 모 회사 입사 테스트 문제 풀이 1/4 (0) | 2014.10.12 |
페북에 올라온 Zig-zag scan 풀이… (0) | 2014.10.09 |
임의의 숫자가 제곱수인지 빠르게 판별하는 법 (5) | 2012.06.27 |