欧拉筛及例题

我们先来看一下最经典的埃拉特斯特尼筛法。时间复杂度为O(n loglog n)

[cpp]  view plain  copy
  1. int ans[MAXN];  
  2. void Prime(int n)  
  3. {  
  4.     int cnt=0;  
  5.     memset(prime,1,sizeof(prime));  
  6.     prime[0]=prime[1]=0;  
  7.     for(int i=2;i<n;i++)  
  8.     {  
  9.         if(vis[i])  
  10.         {  
  11.            ans[cnt++]=i;//保存素数   
  12.            for(int j=i*i;j<n;j+=i)//i*i开始进行了稍微的优化  
  13.            prime[j]=0;//不是素数   
  14.         }  
  15.     }  
  16.     return ;  
  17. }  

显然,当一个数是素数的时候,那么他的倍数肯定是合数,筛选标记即可。从i*i而不从i*2开始,是因为已经i*3,i*2早已经被2,3筛过了。

由此,我们也可以发现有的合数被重复筛除,例如30,2*15筛了一次,5*6重复筛除,所以也就有了我们下面要提到的欧拉线性筛法。

不会重复筛除,是线性O(n)的复杂度。

[cpp]  view plain  copy
  1. const int MAXN=3000001;  
  2. int prime[MAXN];//保存素数   
  3. bool vis[MAXN];//初始化   
  4. void Prime(int n)  
  5. {  
  6.     int cnt=0;  
  7.     memset(vis,0,sizeof(vis));  
  8.     for(int i=2;i<n;i++)  
  9.     {  
  10.         if(!vis[i])  
  11.         prime[cnt++]=i;  
  12.         for(int j=0;j<cnt&&i*prime[j]<n;j++)  
  13.         {  
  14.             vis[i*prime[j]]=1;  
  15.             if(i%prime[j]==0)//关键   
  16.             break;  
  17.         }  
  18.     }  
  19.     return cnt;//返回小于n的素数的个数   
  20. }  
首先,先明确一个条件,任何合数都能表示成一系列素数的积。
然后利用了每个合数必有一个最小素因子,每个合数仅被它的最小素因子筛去正好一次。所以为线性时间。
代码中体现在:
if(i%prime[j]==0)break;
prime数组 中的素数是递增的,当 i 能整除 prime[j],那么 i*prime[j+1] 这个合数肯定被 prime[j] 乘以某个数筛掉。
因为i中含有prime[j], prime[j] 比 prime[j+1] 小。接下去的素数同理。所以不用筛下去了。
在满足i%prme[j]==0这个条件之前以及第一次满足改条件时,prime[j]必定是prime[j]*i的最小因子。


 prime[]数组中的素数是递增的,当i能整除prime[j],那么i*prime[j+1]这个合数肯定被prime[j]乘以某个数筛掉。
因为i中含有prime[j],prime[j]比prime[j+1]小,即i=k*prime[j],那么i*prime[j+1]=(k*prime[j])*prime
[j+1]=k’*prime[j],接下去的素数同理。所以不用筛下去了。因此,在满足i%prime[j]==0这个条件之前以及第一次
满足改条件时,prime[j]必定是prime[j]*i的最小因子。

如果还不是很理解,可以手动模拟一下。

直接应用的一个简单例子。求n以内的素数个数。

http://blog.youkuaiyun.com/nk_test/article/details/46242311

欧拉函数:数论,对正整数n,欧拉函数是少于或等于n的数中与n互质的数的数目。

先给出一个结论:

 设P是素数,

 若p是x的约数,则E(x*p)=E(x)*p.

 若p不是x的约数,则E(x*p)=E(x)*E(p)=E(x)*(p-1).

证明如下:

E(x)表示比x小的且与x互质的正整数的个数。
*若p是素数,E(p)=p-1。
*E(p^k)=p^k-p^(k-1)=(p-1)*P^(k-1)
证:令n=p^k,小于n的正整数数共有n-1即(p^k-1)个,其中与p不质的数共[p^(k-1)-1]个(分别为1*p,2*p,3*p...p(p^(k-1)-1))。
所以E(p^k)=(p^k-1)-(p^(k-1)-1)=p^k-p^(k-1).得证。
*若ab互质,则E(a*b)=E(a)*E(b),欧拉函数是积性函数.
*对任意数n都可以唯一分解成n=p1^a1*p2^a2*p3^a3*...*pn^an(pi为素数).
则E(n)=E(p1^a1)*E(p2^a2)*E(p3^a3)*...*E(pn^an)     
      =(p1-1)*p1^(a1-1)*(p2-1)*p2^(a2-1)*...*(pn-1)*pn^(an-1)
      =(p1^a1*p2^a2*p3^a3*...*pn^an)*[(p1-1)*(p2-1)*(p3-1)*...*(pn-1)]/(p1*p2*p3*...*pn)
      =n*(1-1/p1)*(1-1/p2)*...*(1-1/pn)
* E(p^k)    =(p-1)*p^(k-1)=(p-1)*p^(k-2)*p
  E(p^(k-1))=(p-1)*p^(k-2)
->当k>1时,E(p^k)=E(p*p^(k-1))=E(p^(k-1))*p.
  (当k=1时,E(p)=p-1.)
由上式: 设P是素数,
  若p是x的约数,则E(x*p)=E(x)*p.
  若p不是x的约数,则E(x*p)=E(x)*E(p)=E(x)*(p-1).   证明结束。

http://acm.hdu.edu.cn/showproblem.php?pid=2824  具体的应用

求一段区间的欧拉函数的和。

[cpp]  view plain  copy
  1. #include<iostream>  
  2. #include<string>  
  3. #include<cstring>  
  4. using namespace std;  
  5. const int MAXN=3000001;  
  6. int prime[MAXN];//保存素数   
  7. bool vis[MAXN];//初始化   
  8. int phi[MAXN];//欧拉函数   
  9. void Prime(int n)  
  10. {  
  11.     int cnt=0;  
  12.     memset(vis,0,sizeof(vis));  
  13.     for(int i=2;i<n;i++)  
  14.     {  
  15.         if(!vis[i])  
  16.         {  
  17.             prime[cnt++]=i;  
  18.             phi[i]=i-1;// if p is prime,then phi[i]=i-1  
  19.         }  
  20.         for(int j=0;j<cnt&&i*prime[j]<n;j++)  
  21.         {  
  22.             __int64 k=i*prime[j];  
  23.             vis[k]=1;  
  24.             if(i%prime[j]==0)//关键   
  25.             {  
  26.                 phi[k]=phi[i]*prime[j];  
  27.                 break;  
  28.             }  
  29.             else  
  30.             phi[k]=phi[i]*(prime[j]-1);  
  31.           
  32.         }  
  33.     }  
  34. }  
  35. int main()  
  36. {  
  37.     int a,b;  
  38.     Prime(3000000);  
  39.     while(cin>>a>>b)  
  40.     {  
  41.         __int64 ans=0;  
  42.         for(int i=a;i<=b;i++)  
  43.         ans+=phi[i];  
  44.         cout<<ans<<endl;  
  45.     }  
  46. }  

还有 http://acm.hdu.edu.cn/showproblem.php?pid=3501  

分析:对于整数n,如果x(x<n)与n互质,那么(n-x)也与n是互质的;同理如果x(x<n)与n不互质,那么(n-x)也与n是不互质的。知道这个之后就可以得出:在0<x<n时,存在这样的x与n互质的个数假设为num(可以通过欧拉函数求得),那么所有与n互质的x的和sum=num*n/2.

[cpp]  view plain  copy
  1. /*利用欧拉函数即可求解,1~n比n小且与n互素的数的总和为 
  2. sum(n) = n * phi(n) / 2;那么可以先求出1~n-1的总和,然后 
  3. 减去sum(n)即可。*/  
  4. #include<iostream>  
  5. #include<cstdio>  
  6. #include<cstring>  
  7. #include<algorithm>  
  8. #include<cmath>  
  9. using namespace std;  
  10. typedef long long LL;  
  11. #define MOD 1000000007  
  12. LL n;  
  13. LL Eular(LL n) {  
  14.     LL cnt=1;  
  15.     for(int i=2; i*i<=n; i++) {  
  16.         if(n%i==0) {  
  17.             cnt*=(i-1);  
  18.             n/=i;  
  19.             while(n%i==0) {  
  20.                 n/=i;  
  21.                 cnt*=i;  
  22.             }  
  23.         }  
  24.     }  
  25.     if(n>1)cnt*=(n-1);  
  26.     return cnt;  
  27. }  
  28.   
  29. int main() {  
  30.     while(~scanf("%lld",&n)&&n) {  
  31.         LL ans=(n+1)*n/2-n;  
  32.         ans-=Eular(n)*n/2;  
  33.         printf("%I64d\n",(ans%MOD+MOD)%MOD);  
  34.     }  
  35.     return 0;  
  36. }  
再来一道例题
 prime[]数组中的素数是递增的,当i能整除prime[j],那么i*prime[j+1]这个合数肯定被prime[j]乘以某个数筛掉。
因为i中含有prime[j],prime[j]比prime[j+1]小,即i=k*prime[j],那么i*prime[j+1]=(k*prime[j])*prime
[j+1]=k’*prime[j],接下去的素数同理。所以不用筛下去了。因此,在满足i%prime[j]==0这个条件之前以及第一次
满足改条件时,prime[j]必定是prime[j]*i的最小因子。

F - 粗心的谭爷

Time Limit: 1000/1000MS (Java/Others)    Memory Limit: 262144/262144KB (Java/Others)

谭爷喜欢把整数分解质因数,如12=22∗3

,1024=210,特别地,1=1

有一天谭爷心血来潮,把1

n

所有整数的分解形式写在纸上,然后计算它们的和。

但粗心的谭爷把公式中的幂运算的指数看成了连接在底数后面的数字,如把22∗3

看成了22∗3=66,把210看成了210=210

,因此结果与真实答案大相径庭。

谭爷没有发现这一点,并愚蠢的继续算下去。

坐在谭爷旁边的你看不下去了,写个程序帮谭爷算出他最终得到的结果。

Input

输入一个整数n

,1≤n≤107

Output

输出谭爷算出来的结果。

解题思路:

利用线性筛素数的做法,

考虑两种情况:

(1)i%prime[j]==0:

则对应的i*prime[j]的times[i*prime[j]]++;

(2) i%prime[j]!=0;

则i不含prime[j]这个因子;

则ans[i*prime[j]]=ans[i]*calc(minprime[i],times[i]);

代码:

[cpp]  view plain  copy
  1. #include<bits/stdc++.h>  
  2. using namespace std;  
  3. #define MAXN 10000005  
  4. long long prime[MAXN/2],times[MAXN],minprime[MAXN];//prime记录素数,times记录i被最小的质因子筛了多少次,minprime记录最小的质因子;   
  5. long long ans[MAXN];//记录看错后的数字   
  6. bool flag[MAXN];//记录有没有被筛   
  7. long long calc(long long num,long long t)  
  8. {  
  9.     if(t==1)  
  10.     return num;  
  11.     else if(t>1&&t<10)  
  12.     return num*10+t;  
  13.     else if(t>=10&&t<100)  
  14.     return num*100+t;  
  15. }  
  16. int main()  
  17. {  
  18.     long long n,t=0;  
  19.     long long sum=1;  
  20.     long long i,j;  
  21.     memset(flag,false,sizeof(flag));  
  22.     scanf("%lld",&n);  
  23.     for(i=2;i<=n;i++)  
  24.     {  
  25.         if(!flag[i])//对于素数,可以直接考虑;   
  26.         {  
  27.             prime[t++]=i;  
  28.             minprime[i]=i;  
  29.             ans[i]=1;  
  30.             times[i]=1;  
  31.         }  
  32.         for(j=0;j<t&&prime[j]*i<=n;j++)  
  33.         {  
  34.             flag[prime[j]*i]=true;  
  35.             minprime[prime[j]*i]=prime[j];  
  36.             if(i%prime[j]!=0)  
  37.             {  
  38.                 ans[i*prime[j]]=ans[i]*calc(minprime[i],times[i]);  
  39.                 times[i*prime[j]]=1;  
  40.             }  
  41.             else  
  42.             {  
  43.                 ans[i*prime[j]]=ans[i];  
  44.                 times[i*prime[j]]=times[i]+1;  
  45.                 break;  
  46.             }  
  47.         }  
  48.         sum+=ans[i]*calc(minprime[i],times[i]);//由于之前只是计算了非最小质因子的结果,所以还要加上一项;   
  49.     }  
  50.     printf("%lld",sum);  
  51.     return 0;  
  52. }  
prime[]数组中的素数是递增的,当i能整除prime[j],那么i*prime[j+1]这个合数肯定被prime[j]乘以某个数筛掉。
因为i中含有prime[j],prime[j]比prime[j+1]小,即i=k*prime[j],那么i*prime[j+1]=(k*prime[j])*prime
[j+1]=k’*prime[j],接下去的素数同理。所以不用筛下去了。因此,在满足i%prime[j]==0这个条件之前以及第一次
满足改条件时,prime[j]必定是prime[j]*i的最小因子。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值