프로젝트 오일러 41
projecteuler, cpp, next_permutation팬디지털 넘버들의 목록을 구한다는 것은 곧 1~9까지의 숫자들로 순열을 만드는 과정이다. 오일러 32번 에서 팬디지털 넘버를 만들기 위한 순열 코드를 만들었고, 오일러 35번 에서 소수를 판별하는 코드를 만들었으므로 갖다 쓰기로 했다.
느린 풀이
/*
Problem 41 - Pandigital prime
*/
#include <iostream>
#include <ctime>
#include <vector>
#include <cmath>
using namespace std;
int largest = 0; // solution
bool isprime( int n)
{
if( n == 2) return true;
int s = sqrt( n) + 1;
for(int i=s; i>1; i--)
{
if (n%i ==0) { return false; }
}
return true;
}
void permutation( std::vector<int>nums, std::vector<int> now)
{
if (nums.size() == 0) {
int pandigitalNum = 0;
for(int i=0;i<now.size();i++)
{
pandigitalNum += now[i] * pow(10, i);
}
if (isprime( pandigitalNum)) {
cout << "pandigital prime : " << pandigitalNum << endl;
if ( largest < pandigitalNum) {
largest = pandigitalNum;
}
}
return;
}
for(int i=0; i<nums.size(); i++)
{
std::vector<int> newone( now);
std::vector<int> newnums( nums);
int val = newnums[i];
newone.push_back( val);
newnums.erase( newnums.begin() + i);
permutation( newnums, newone);
}
}
int main(int argc, char** argv)
{
clock_t begin = clock();
/* starting code */
for(int i=1; i<=9; i++)
{
std::vector<int> nums;
for(int j=1; j<=i; j++)
{
nums.push_back( j);
}
std::vector<int> now;
permutation( nums, now);
}
cout << endl << "largest pandigital Num = " << largest << endl;
/* end of code */
clock_t end = clock();
std::cout << "elapsed time=" << double(end - begin) / CLOCKS_PER_SEC << std::endl;
return 0;
}
답은 구했지만 21.1168 초가 걸린다. 더 빠른 방법이 없을까?
아주 약간 개선한 풀이
위의 코드는 가장 작은 자릿수의 팬디지털 넘버부터 소수 여부를 따져보고 있다. 문제에서는 가장 큰 수를 구해야 하므로, 루프를 큰 수부터 거꾸로 돌고 max 를 찾으면 바로 그만 돌도록 바꿔 보았다.
헌데 이것만으로는 단축되지 않았다. 끽해야 0.02초 차이..
순열 함수에서 vector 를 남용한 건가 싶어, 내가 직접 짠 순열 함수 대신 대신 std::next_permutation 을 사용해도, 실행시간은 20.5초.. 많이 줄지 않았다.
#include <algorithm>
int main(int argc, char** argv)
{
clock_t begin = clock();
/* starting code */
for(int i=9; i>=1; i--)
{
int nums[9] = {0,};
for(int j=0; j<i; j++)
{
nums[j] = i-j;
}
std::sort( nums, nums+i);
do {
int pandigital = 0;
for(int j=0; j<i; j++)
{
pandigital += nums[j] * pow(10, j);
//cout << nums[j];
}
cout << "pan = " << pandigital << endl;
if( isprime( pandigital)) {
if( largest < pandigital) {
largest = pandigital;
}
}
//cout << endl;
} while( std::next_permutation( nums, nums+i));
if (largest != 0) {
break;
}
}
cout << endl << "largest pandigital Num = " << largest << endl;
/* end of code */
clock_t end = clock();
std::cout << "elapsed time=" << double(end - begin) / CLOCKS_PER_SEC << std::endl;
return 0;
}
효율적으로 제외하기
구글링을 해보니 검사할 팬디지털 자리수를 아주 센스있게 대폭 줄일 수 있다.
어떤 팬디지털 수의 각 자릿수를 모두 더한 값은, 같은 자릿수 크기를 가지고 있는 다른 팬디지털 수의 경우에도 모두 같다. 그리고 각 자릿수의 합이 3 의 배수라면, 그 수는 3의 배수이다. 이를 이용해 특정 자리수의 팬디지털 수 검사를 통째로 넘어갈 수 있다.
위 코드에 간단히 sumcheck
변수를 넣어 체크해 주었다.
int main(int argc, char** argv)
{
clock_t begin = clock();
/* starting code */
for(int i=9; i>=1; i--)
{
int nums[9] = {0,};
int sumcheck = 0;
for(int j=0; j<i; j++)
{
nums[j] = i-j;
sumcheck += nums[j];
}
if (sumcheck%3 == 0) continue;
std::sort( nums, nums+i);
do {
int pandigital = 0;
for(int j=0; j<i; j++)
{
pandigital += nums[j] * pow(10, j);
//cout << nums[j];
}
cout << "pan = " << pandigital << endl;
if( isprime( pandigital)) {
if( largest < pandigital) {
largest = pandigital;
}
}
//cout << endl;
} while( std::next_permutation( nums, nums+i));
if (largest != 0) {
break;
}
}
cout << endl << "largest pandigital Num = " << largest << endl;
/* end of code */
clock_t end = clock();
std::cout << "elapsed time=" << double(end - begin) / CLOCKS_PER_SEC << std::endl;
return 0;
}
여기서 9자리 팬디지털 수와 8자리 팬디지털 수가 계산에서 제외되었고, 실행시간은 0.045초로 매우 빨라졌다. 9자리/8자리 팬디지털 수의 경우의 수가 너무 많아, 이를 계산하는 데 대부분의 시간을 쓰고 있었구나..