昨天的考試跪的一塌糊塗:第一題水過,第二題帶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 }
