kmjp's blog

競技プログラミング参加記です

yukicoder : No.575 n! / m / m / m...

あの公式競技プログラミングで初めて使った。
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は数学系の知識を問う問題多いんだよな。