算法思路来自 。本文对问题的基本原理进行解释,以及对代码进行了标注,仅用作学习用途。
一、题目描述:
给定n,a求最大的k,使n!可以被a^k整除但不能被a^(k+1)整除。
输入描述:
两个整数,n(2≤n≤1000),a(2≤a≤1000)
输出描述:
一个整数。
示例1:
输入:6 10
输出:1
二、分析:考虑最大的输入n=1000,则 n! 将会是十分大的整数,所以用常规数据类型来存放 n! 然后去整除a的幂次显然是不可行的。
又知道,每一个非素数均可以表示成多个素数的乘积。则n和a,以及a^k将分别可以表示为
①若a可以整除n,则a的任何一个素因子均会在n的素因子集合中出现,即
且q1,q2...qn在a中出现的次数均不超过其在n中出现的次数。以上两个条件必须同时满足,否则a不可以整除n。
有了上述的基础,则可以将整除问题转化为判断两个数的素因子是否满足上述的两个条件。
②接下来,由于是求n的阶乘,所以需要求的是 n! 的素因子及相应的次数。而 n!=1*2*3*....n,包含了从1到n,里面的最大数为n,则可以分析得知 n! 的素因子均是小于 n 的(这样就确定了要求的素数的上界)。
③又,假设n的某个素因子为p,则p的每个倍数(1*p,2*p,...)均为n! 至少提供一个p因子,则总至少提供p因子的个数为 n/p 个;p^2的每个倍数均至少为 n! 提供两个p因子,但是此处与前面p的每个倍数重复了一次,因此总至少提供p因子的个数增加 n/p^2 个。以此类推,则p因子在 n! 中出现的次数可以统计出来。
④对于a的素因子出现的次数,可以用整数分解质因数的算法解决。
⑤既然 a 和 n! 的素因子的出现次数均已统计出来,接下来就需要求最小的整数 k。显然,a 的素因子的次数乘上倍数 k 之后必须不能超过其在 n! 出现的次数,则仅需用其在 n! 中出现的次数除以其在 a 中出现的次数即可得到倍数 k,然后从众多倍数中挑选出最小的一个即可。
按照上述思路:
三、具体代码
#include<iostream>
#include<vector>
#include<cstring>
#include<cmath>
using namespace std;
const int maxn=1001;
vector<int> prime;
bool isprime[maxn];
void prime_init(){ //求出小于n的所有素数
memset(isprime,1,maxn);
isprime[0]=false; isprime[1]=false;
for(int i=2;i<maxn;++i){
if(!isprime[i]){
continue;
}else{
prime.push_back(i);
for(int j=i*i;j<maxn;j+=i){
isprime[j]=false; //素数的倍数为非素数
}
}
}
}
int main(){
prime_init();
int prime_a[maxn][2]; //新建一个二维数组用于存放a的素因子,以及出现的次数
int size=prime.size();
int n, a;
while(scanf("%d %d",&n,&a)!=EOF){
for(int i=0;i<maxn;i++){
prime_a[i][1]=0; //初始化
}
//以下求a的素因子,及其出现的次数
int bound=sqrt(a);
int count=0; //count 用作统计a的素因子个数
for(int i=0;i<size&&prime[i]<bound;i++){
if(a%prime[i]==0){
count++; //素因子+1
prime_a[i][0]=prime[i]; //记录素因子
while(a%prime[i]==0){
a/=prime[i];
prime_a[i][1]++; //素因子出现次数+1
}
}
}
if(a>1){ //当a≠1时,表示此时的a就是素数
prime_a[count][0]=a;
prime_a[count][1]++;
count++;
}
//以下遍历求解a能否整除 n!
int min_times=maxn; //记录最小的倍数k
bool tag=true; //tag 作为能够整除的标志
int j, c; //j 用作素因子的幂指数,c 用作记录素因子在 n! 中出现的次数
for(int i=0;i<count&&tag;i++){
if(n/prime_a[i][0]==0){
tag=false;
break; // n! 中包含有从1到n,所以当存在a的某个素因子比n还大,则a必不可以整除n!
}else{
j=1; c=0;
while(n/prime_a[i][0]>0){ //需要注意此处的判断条件与上面if的判断条件,原理一致
int temp=(int)pow(double(prime_a[i][0]),double(j)); //temp 存放素因子的幂次
c+=n/temp; //不断累加素因子在n! 中出现的次数
j++;
if(n/temp==0){
break; //当素因子的幂次大于n时,不用再继续求了
}
}
if(c<prime_a[i][1]){
tag=false; //若a中某个素因子的出现次数大于其在n!中出现的次数,则a比不可以整除n!
}else{
c /=prime_a[i][1]; //除法求倍数k
if(min_times>c){
min_times=c; //得出最小的k
}
}
}
}
if(tag){
printf("%d\n",min_times); //输出最小k
}else{
printf("-1\n"); //不能整除输出"-1"
}
}
return 0;
}