📖
ABC280 D - Factorial and Multiple(C++)
問題概要
問題文
以上の正の整数 2 が与えられる。 K
正の整数であって、 N が N! の倍数になるようなもののうち最小のものを求めよ。 K
なお、このようなが存在することは証明できる。 N
制約
2\leq K\leq 10^{12}
メモ
考えたこと
- まず、
がでっかいからK とかは無理。かといって、O(K) で求まりそうにもない...O(1) - テストケースを見ると、探索範囲も広そう...
- 単調性があるので、二分探索ベースかなぁ...
- 答えは最大でも
だから、探索範囲もわかる(K より、\because K!=K\times\cdots\times1 はK! の倍数)K -
がN! の倍数というところを高速に処理できれば...K
解法
上にも書いた通り、二分探索で解ける。というのは、次が成り立つから
- ある
に対して、n がn! の倍数であったとする。この時、任意のK に対してm>n はm! の倍数K
これは、
要するに、ある境界を超えると、先はずっと条件を満たしてくれる。そして、その境界が答え!!!
任意の
に対して次が成り立つ: i
を素因数分解したとき、 n! の指数は p_i 以上 e_i
ここで、ルジャンドルの定理を使う。ルジャンドルの定理とは以下のようなもの。
正の整数
に対して、 n を素因数分解したとき、素数 n! の指数は p
\sum_{e=1}^{\infty}{\left[ \frac{n}{p^e} \right]}
である
詳しいことは、こちら
これを使うと、条件は
任意の
に対して次が成り立つ: i
\sum_{j=1}^{\infty}{\left[ \frac{n}{p_i^j} \right]}\geq e_i
となる。
-
の取りうる範囲:素因数の種類数に等しい。多く見積もってもi O(\log K) -
の取りうる範囲:j O(\log n)
したがって、
実装例
- 素因数分解に
O(\sqrt K) - 二分探索で
O(\log K) -
- 判定に
O(\log K\log n)
- 判定に
素因数分解のところがボトルネックになり、全体では
#include<iostream>
#include<vector>
using namespace std;
using ll=long long;
//素因数分解
vector<pair<ll,ll>> prime_factorization(ll n){
vector<pair<ll,ll>> res;
for(ll p=2;p*p<=n;p++){
if(n%p!=0)continue;
ll e=0;
while(n%p==0){
e++;
n/=p;
}
res.emplace_back(p,e);
}
if(n!=1)res.emplace_back(n,1); //素数の場合
return res;
}
int main(){
ll k;
cin>>k;
auto fact=prime_factorization(k);
//n!がkの倍数か?
auto is_ok=[&](ll n)-> bool {
for(auto pf:fact){
ll p=pf.first;
ll e=pf.second;
ll e2=0; //n!を素因数分解したときのpの指数
ll pow=p;
while(n/pow!=0){
e2+=n/pow;
pow*=p;
}
if(e>e2)return false; //eのほうが大きかったらfalse
}
//全部クリアしたらtrue
return true;
};
//二分探索
ll ok=k; //k!はkの倍数
ll ng=0; //0!=1は、kの倍数にはなり得ない
while(abs(ng-ok)>1){
ll mid=ng+(ok-ng)/2;
if(is_ok(mid))ok=mid;
else ng=mid;
}
cout<<ok<<endl;
}
Discussion