あの公式競技プログラミングで初めて使った。
https://yukicoder.me/problems/no/575
問題
正整数N,Mが与えられる。
N!をMで割り切れなくなるまでMで割ったとき、その値を指数表記で答えよ。
なお誤差は1%まで許容される。
解法
Nの上限は10^12であり、N!がとても大きいことと、答え方が指数表記かつ誤差の許容範囲が大きいことから、対数で処理すればよいことに気付くとラク。
まずN!をMで何回割れるかは、Mの素因数でN!を何回割れるか求めれば求められる。
例えばMでa回割り切れるとき、N!/(M^a)の近似値を求めよう。
Nが10^6程度までと余り大きくないなら、N!は愚直に対数の足し算で求めればよい。
Nが大きい場合、スターリングの公式を使おう。
幸いこの式は除算乗算平方根しか使わないため、対数値も容易に求めることができる。
N!/(M^a)における10を底とした対数を求められれば、桁数もわかるし仮数部も求められる。
ll N,M; map<ll,int> enumpr(ll n) { map<ll,int> V; for(ll i=2;i*i<=n;i++) while(n%i==0) V[i]++,n/=i; if(n>1) V[n]++; return V; } ll numdiv(ll a,ll B) { ll ret=0; while(B>=a) { ret+=B/a; B/=a; } return ret; } void solve() { int i,j,k,l,r,x,y; string s; cin>>N>>M; auto A=enumpr(M); ll ma=1LL<<60; FORR(a,A) { ma=min(ma,numdiv(a.first,N)/a.second); } double a=0; if(N>100000) { a=N*(log(N)-1)+log(sqrt(2*N*atan(1)*4)); } else { for(i=N;i>=1;i--) a+=log(i); } a -= ma*log(M); double c=a/log(10); double P=c-floor(c); double Q=floor(c)+0.5; _P("%.12lfe%lld\n",exp(P*log(10)),(ll)Q); }
まとめ
地味にyukicoderは数学系の知識を問う問題多いんだよな。