線性篩(歐拉篩)


昨天的考試跪的一塌糊塗:第一題水過,第二題帶WA的朴素,最后題忘了特判左端點全跪,分數比起預計得分整整打了個對折啊!

步入正題:線性篩(歐拉篩)

一般的篩法(PPT里叫埃拉托斯特尼篩法,名字異常高貴)的效率是O(NlglgN)(其實很接近O(n)啊!),對於一些例如N=10000000的殘暴數據會跪,於是,線性篩登場了…

 1 #include <cstring>
 2 using namespace std;
 3 int prime[1100000],primesize,phi[11000000];
 4 bool isprime[11000000];
 5 void getlist(int listsize)
 6 {
 7     memset(isprime,1,sizeof(isprime));
 8     isprime[1]=false;
 9     for(int i=2;i<=listsize;i++)
10     {
11         if(isprime[i])prime[++primesize]=i;
12          for(int j=1;j<=primesize&&i*prime[j]<=listsize;j++)
13          {
14             isprime[i*prime[j]]=false;
15             if(i%prime[j]==0)break;
16         }
17     }
18 }

 

以上是線性篩代碼。

就我的理解,線性篩有兩個地方與一般篩不同:

1.兩層循環的順序不同(一般篩是第一維prime[i] 第二維j,歐拉篩是第一維i 第二位prime[j])

2.一行神奇的代碼:

14             if(i%prime[j]==0)break;

這行代碼神奇地保證了每個合數只會被它的最小素因子篩掉,就把復雜度降到了O(N)。

接下來是證明這個算法正確性的說明:

 P.S.因為自己很蠢,從下午開始研究,吃完飯之后看了部電影,百度了下,找到一個講的超好的網站,它的說法如下:

   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的最小因子。

之前看了網上各種各樣的說法,整個理解都有問題,總算是明白了。

顯然,線性篩只拿來篩篩素數是很不科學的,它的速度大約是一般篩的3~4倍,在數據量小的時候甚至慢些(用到了mod運算)。

接下來就是它的重量級應用了:求解積性函數

 P.S.睡了一覺,精神抖擻!

積性函數f(x)應滿足f(a×b)=f(a)×f(b),a與b應互素。而完全積性函數則應滿足對於任意的a與b,前面的等式應成立。

先是歐拉phi函數(反演不怎么懂):

 1 void getlist(int listsize)
 2 {
 3     memset(isprime,1,sizeof(isprime));
 4     isprime[1]=false;
 5     for(int i=2;i<=listsize;i++)
 6     {
 7         if(isprime[i])
 8         {
 9              prime[++primesize]=i;
10              phi[i]=i-1;
11          }
12          for(int j=1;j<=primesize&&i*prime[j]<=listsize;j++)
13          {
14             isprime[i*prime[j]]=false;
15             if(i%prime[j]==0)
16              {
17                 phi[i*prime[j]]=phi[i]*prime[j];
18                 break;
19             }
20             phi[i*prime[j]]=phi[i]*(prime[j]-1);
21         }
22     }
23 }

至於為什么這樣的遞推是成立的,有如下的幾個原因:

  1.每個合數只會被篩到一次(前面說明過)。

  2.當正整數p是素數時,phi[p]=p-1。

  3.phi函數是一個積性函數,當a與b互素時,滿足phi(a×b)=phi(a)×phi(b)。

  4.上述代碼中的i永遠大於等於prime[j],又因為prime[j]必然是素數,所以i、prime[j]互素當且僅當i=0 (mod prime[j])。

  5.當p是素數時,phi(pk)=(p-1)×pk-1

原因1保證了每個數的phi只會被計算一次,第10行的代碼可以由原因2解釋,第20行的代碼可以由原因2與原因3解釋,第17行的代碼可以由原因5所解釋。

還是覺得這個算法好神奇……

接下來的東西就和昨天的考試有關了,先是求每個數的因數個數:

記每個數因數個數為e(i),顯然函數e(i)是積性函數但不是完全積性函數。

首先,當p是素數時e(i)=2。

對於i與prime[j]互素的情況,直接相乘即可。而對於i與prime[j]不互素的情況,就有點復雜了。

我們要再記錄一個num數組,num[i]表示i的最小素因子(即每次遞推時的prime[j])的次數。

對於i與prime[j]不互素的情況,e(i×prime[j])=e(i)÷(num[i]+1)×(num[i]+2)。

num數組的維護也是很方便的:對於所有n為素數或n=i×prime[j](i與prime[j]互素)的情況,num(i)=1,對於另外一種情況num[i*prime[j]]=num[i]+1。

再是求因數和還有因數平方和(兩者差不了多少)

設數n的因數和為q(n),有q(n)=(1+p1+p12+...+p1k1)×(1+p2+p22+...+p2k2)×...×(1+pn+pn2+...+pnkn)。

顯然,函數q是積性的,我們只用考慮i與prime[j]不互質的情況,一種辦法是用乘法逆元搞,顯然這是不科學的,真正的做法如下

q(i×prime[j])=q(i÷prime[j]num[i])×q(prime[j]num[i]+1)

其中的prime[j]num[i]是可以預先存好的。這種做法還是利用了積性函數的性質。

最后附上昨天第二題的代碼

 1 #include <iostream>
 2 #include <cstdio>
 3 using namespace std;
 4 inline long long imax(long long a,long long b){return a>b?a:b;}
 5 const int mo=1000000007;
 6 long long q[2100000];
 7 long long prime[11000000],pnum,ynum[11000000],ysum[11000000],c[11000000];
 8 bool isprime[10000001];
 9 void getlist(int listsize)
10 {
11     memset(isprime,1,sizeof(isprime));
12     ynum[1]=ysum[1]=c[1]=1;
13     isprime[1]=false;
14     for(int i=2;i<=listsize;i++)
15     {
16         if(isprime[i])
17         {
18             prime[++pnum]=i;
19             c[i]=i;ynum[i]=2;
20             ysum[i]=((long long)i*(long long)i+1)%mo;
21         }
22         for(int j=1;j<=pnum&&i*prime[j]<=listsize;j++)
23         {
24             int num=i*prime[j];
25             isprime[num]=false;
26             if(i%prime[j]==0)
27             {
28                 c[num]=c[i]*prime[j];
29                 if(num!=c[num])
30                 {
31                     ynum[num]=(long long)ynum[c[num]]*(long long)ynum[num/c[num]]%mo;
32                     ysum[num]=(long long)ysum[c[num]]*(long long)ysum[num/c[num]]%mo;
33                 }
34                 else 
35                 {
36                     ynum[num]=(ynum[i]+1)%mo;
37                     ysum[num]=(ysum[i]+(long long)num*(long long)num)%mo;
38                 }
39                 break;
40             }
41             else 
42             {
43                 c[num]=prime[j];
44                 ynum[num]=(long long)ynum[i]*(long long)ynum[prime[j]]%mo;
45                 ysum[num]=(long long)ysum[i]*(long long)ysum[prime[j]]%mo;
46             }
47         }
48     }
49 }
50 int main(int argc, char *argv[])
51 {
52     freopen("math.in","r",stdin);
53     freopen("math.out","w",stdout);
54     int n;scanf("%d",&n);
55     long long a,b,c;scanf("%I64d%I64d%I64d%I64d",&q[1],&a,&b,&c);
56     long long biggest=q[1];
57     for(int i=2;i<=n;i++)
58     {
59         q[i]=((long long)q[i-1]*a+b)%c+1;
60         biggest=imax(biggest,q[i]);
61     }
62     getlist(biggest);
63     long long ansnum=0,anssum=0;
64     for(int i=1;i<=n;i++)
65     {
66         ansnum=(ansnum+ynum[q[i]])%mo;
67         anssum=(anssum+ysum[q[i]])%mo;
68         if((q[i]&1)&&q[i]!=1)
69         {
70             ansnum=(ansnum+1)%mo;
71             anssum=(anssum+4)%mo;
72         }
73     }
74     cout<<ansnum<<endl<<anssum<<endl;
75     return 0;
76 }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM