数学基础:素数筛法、分解素因数
首先强调:0、1不是素数。
当求较大范围内符合特殊要求的素数时,若预处理时间太长,可以打表。先把答案跑出来,放入表中直接读取即可。洛谷P1218
判断一个数是不是素数,可以用O(sqrt(n))的复杂度来实现
bool judge(int x)
{
int y=sqrt(x)+1;
for(int i=2;i<=y;i++){
if(x%i==0){
return false;
}
}
return true;
}
当求一个范围内的素数个数或是素数具体的值时,一个一个判断就速度太慢。这种情况采用素数筛法进行处理。首先是普通筛法(埃氏筛法),预先进行处理,记录下来素数的值,便于之后查询。
void init(int n)
{
for(int i=0;i<=n;i++){
mark[i]=false;
}
cnt=0;
for(int i=2;i<=n;i++){
if(mark[i]) continue;
prime[++cnt]=i;
for(int j=i*i;j<=n;j+=i){ //注意int*int可能会溢出
mark[j]=true;
}
}
}
但是普通筛法会对合数进行重复处理,对此进一步优化有欧式筛法。欧式筛法复杂度为O(n),主要优越之处在于避免了对合数的重复处理,从而加快了处理时间。因此不仅可以统计一定范围内的素数,而且可以较为快速的求出素数的数目。
void init()
{
for(int i=0;i<=n;i++){
mark[i]=false;
}
cnt=0;
for(int i=2;i<=n;i++){
if(!mark[i]){
prime[++cnt]=i;
}
for(int j=1;j<=cnt;j++){
if(i*prime[j]>n) break;
mark[i*prime[j]]=true;
//下面的判断是算法核心,每次都使prime[j]为当前合数的最小因子,如此才能避免对合数的重复处理
if(i%prime[j]==0) break;
}
}
}
对于复杂度要求更高的题目来说,欧式筛法也不能满足。MEISSEL-LEHMER算法,复杂度为O(N^(2/3))。但是太复杂了,一般也用不到,有需要的可以自己了解......
分解素因数,即将一个数分解为a=(p1^e1)*(p2^e2)*(p3^e3)......思路是预先处理出sqrt(n)范围内的素数,然后遍历已有素数。对当前素数一直除,直到a%prime[i]!=0,同时进行计数;对每个素数进行这样的处理,直到a=1;若到最后a!=1,则有超出sqrt(n)素数范围的一个大素数。这样就完成了素因数的分解。
但是对于n!,情况要发生变化。因为n!太大了,算出来n!再求素因数不合适。因此对于n!的素因数分解,有以下方法:
for(int i=0;i<primeSize;i++){
cnt[i]=0;
}
//对n!分解素因数,遍历每个素数
for(int i=0;i<primeSize;i++){
int t=n; //用t作为临时变量
//依次计算t/num[i]^k,累加其值,直到t变为0
while(t){
cnt[i]+=t/num[i];
t=t/num[i];
}
}
这样处理的考虑是:n!包含了1到n的数的乘积。对于一个作为素因数的数p,1到n中可以提供一个p的数,应当有n/p个。那么,可以提供p^2的数,应当有n/p^2个。考虑到这个过程中有重复,所以直接累加即可。
求组合数时,C(n,r)=n!/(r!*(n-r)!),如果直接计算阶乘会出问题,可以采用以下方法
#include<stdio.h> #include<string.h> typedef long long ll; ll gcd(ll a,ll b){ if(b==0) return a; return gcd(b,a%b); } ll factorial(ll n,ll m){ if(n==0||m==0) return 1; m=n-m>m?m:n-m; ll a=1,b=n-m+1,ans=1,cnt=1; while(cnt<=m){ ll tmp=gcd(b,a); ans=ans*(b/tmp)/(a/tmp); a++;b++; cnt++; } return ans; } int main(){ int n,m; while(scanf("%d%d",&n,&m)!=EOF){ if(n==0&&m==0) break; printf("%lld\n",factorial(n,m)); } return 0; }


浙公网安备 33010602011771号