1. double 의 표현범위는 가수부 정확도를 무시하고 10의 308승 정도다.
당연히 문제는 100의 100만승 의 범위를 이야기 하니까. double 을 이용하는 pow 따위 노답.
2. 각 진법의 곱셈은 제일 아랫자리의 순환으로 수렴된다. 문제의 특수성을 이해하는게 중요.
즉,
pow 는 일반해
표현범위는 일반론의 한계
끝자리 순환은 특수해를 위한 특징
대략
#include <iostream>
#define rigid constexpr
#define countof( arr ) ( sizeof arr / sizeof *arr )
using uu = std::size_t;
const uu radix = 10; // 진법
inline auto count_of_suffixes( const uu a ) // 순환 길이 측정 O( log radix )
{
uu ret = a;
uu len = 0;
do ret = ret * a % radix, len++; while( ret != a );
return len;
}
inline auto suffix_of_pow( const uu a, const uu b ) // a ^ b 표현의 각 진법 마지막 자리 구하기 O( log radix )
{
const uu suffix_a = a % radix;
const uu count = ( b - 1 ) % count_of_suffixes( suffix_a );
uu ret = suffix_a;
for( uu i = 0; i < count; ++i )
ret = ret * suffix_a % radix;
return ret ? ret : radix;
}
이런식으로 특수해가 설명되는데,
이 순환 패턴을 상수로 작성하면 O( 1 ) 이 된다.
rigid uu suffixes0[] = { 0, };
rigid uu suffixes1[] = { 1, };
rigid uu suffixes2[] = { 2, 4, 8, 6, };
rigid uu suffixes3[] = { 3, 9, 7, 1, };
rigid uu suffixes4[] = { 4, 6, };
rigid uu suffixes5[] = { 5, };
rigid uu suffixes6[] = { 6, };
rigid uu suffixes7[] = { 7, 9, 3, 1, };
rigid uu suffixes8[] = { 8, 4, 2, 6, };
rigid uu suffixes9[] = { 9, 1, };
이렇게 10진법 지수표현의 각 밑수 기준 순환테이블을 만들고,
template< class T >
struct ARRAY
{
const T* items;
const uu size;
const T& operator[]( const uu index ) const
{
return items[ index % size ];
}
};
이런식으로 낱개의 서로 다른 길이의 배열을 하나로 묶어줄 wrapper 를 만든 뒤,
#define itm_( n ) { suffixes##n, countof( suffixes##n ), }
rigid ARRAY< uu > suffixes_list[] =
{
itm_(0), itm_(1), itm_(2), itm_(3), itm_(4),
itm_(5), itm_(6), itm_(7), itm_(8), itm_(9),
};
#undef itm_
이렇게 하나의 배열로 묶어주면 되지.
inline auto suffix_of_pow( const uu a, const uu b )
{
auto ret = suffixes_list[ a % radix ][ b - 1 ];
return ret ? ret : radix;
}
이렇게 불러주면 되는것. 완전한 O( 1 ) 이지?
그런데, 순환 배열 상수를 저렇게 암산하는건 아름다운 케이스가 아니지.
다른 진법에서 일일이 계산하기도 귀찮고 말야.
그러니,
template< uu... ns >
struct seq // template variadic parameter 를 수열로 처리하는 구조체
{
static rigid auto size()
{
return sizeof...( ns );
}
static rigid auto to_array()
{
return std::array< uu, size() > { ns... };
}
};
template< uu n, uu radix = 10 >
struct suffix // 자동으로 끝자리 순환 배열을 만드는 구조체
{
template< uu s, uu... ns >
struct make : make< s * n % radix, ns..., s % radix > {};
template< uu... ns >
struct make< n, ns... > : seq< n, ns... > {};
static rigid auto to_array()
{
return make< n * n >::to_array();
}
};
실제로 10개에 대해 만들자.
#define arr_( n ) rigid auto suffixes ## n = suffix< n >::to_array()
arr_(0); arr_(1); arr_(2); arr_(3); arr_(4);
arr_(5); arr_(6); arr_(7); arr_(8); arr_(9);
#undef arr_
아까처럼 묶어주구~
#define itm_( n ) { (const uu*)&suffixes ## n, suffixes ## n.size(), }
rigid ARRAY< uu > suffixes_list[] =
{
itm_(0), itm_(1), itm_(2), itm_(3), itm_(4),
itm_(5), itm_(6), itm_(7), itm_(8), itm_(9),
};
#undef itm_
suffix_of_pow( a, b ) 호출해주면 끝~.
모든 경험은 재사용될 수 있게 코드로 옮겨져야 한다. ( 짬날때 일반화 하자. 그게 니 미래다 )
막글 ㅁㅊㄷㅁㅊㅇ ㄷㄷ - 백왕은 사랑입니다.
츤데레 오지네여..ㅠ 절 위해 손수 이렇게 글도 써주시고.. 사실 어제 그 한마디면 다 이해됫엇긴 하지만 감사해여 ㅠㅠ
응? 위에 글은 쉬워서 노잼인데 아래가 핵심.
근데 일반화 하자는게 무슨말이에여? 재사용 쉽게 바꿔놓으라는건가여?
저기서는 O( 1 ) 알고리즘으로의 최적화를 미리계산된 상수 배열로 구현했고,
그 상수배열을 만드는 과정을 프로그래밍 해버렸지. 어떤 진법에서 사용할 수 있도록 일반화 해서 말야.
진짜 n진법으로 바꿔왔네요
배열 생성도 unroller 로 만들수 있는데 그런 경우 전역으로 쓰기는 불편해져서 득실이 있기 땜시 따로 안함. 항상 일반화는 득이 많은 수준에서 멈추는게 좋아.
어차피 딴 일반화 꺼리도 많으니까 굳이 득실이 공존하는 상황까지 일반화할 필요는 없어.
ㅗㅜㅑ 역시 횽늼 인터넷에 널리고널린 풀이랑은 다르고만
아 근데 struct make< n, ns... > : seq< n, ns... > {}; 요 표현 때문에 VC++ 에선 오류 뜰거야.
VC++ 은 재귀형 탬플릿의 특수화에 상수 가 아닌 상수의 레이블을 쓰면 걍 뻗음. 병신 컴파일러.
즉 아래 코드는 GCC 용임다. 심심한 사람들이 VC++ 로 work around 해 보시길.
청멍//됫->됐 (되어 = 돼임) [리듬 맞춤법 봇♬]
이래서 제가 자바 사무원합니다 - dc App
막줄에 큰 반성하고갑니다
블랙매직 ㅁㅊㄷ