數論模板(1) 質數判斷、線性篩、樸素尤拉函式線性篩
1.素數的判斷
從去年退役之後,本人重回競賽界,開始新的人生,只是忘記的東西太多,一點點地複習吧
先來說質數的判斷
首先,一個數是質數的充分必要條件是,除了1和本身沒有其他因子,(順便說一下1也不是素數,其實1被認為既不是素數也不是合數)。因此最最樸素的演算法是列舉除了1和它本身之間的所有數,判斷是否能整除。
int isprime(int n)
{
if(n==1)return 0;
for(int i = 2; i < n; i ++)
{
if(n%i==0)return 0;
}
return 1;
}
顯然,時間複雜度為O(n)
我們仔細觀察,其實沒有必要列舉到n,列舉到sqrt(n)(表示n的平方根)即可
int isprime(int n)
{
if(n==1)return 0;
for(int i = 2;i*i<=n; i++)//i*i<=n比i<=sqrt(n)更好,因為浮點運算比整數運算慢
{
if(n%i==0)return 0;
}
return 1;
}
時間複雜度優化至O(sqrt(n))
那麼,有沒有更快一點的演算法呢?
有的…
如果在判斷之前完成預處理,將所求範圍內的所有質數存在一個數組中,列舉所有質數進行判斷即可
int isprime(int n)
{
if(n==1)return 0;
for(int i = 1; prime[i]*prime[ i]<= n; i++)
{
if(n%prime[i]==0)return 0;
}
return 1;
}
時間複雜度低於O(sqrt(n))(玄學)
那麼問題來了,質數表如何求出呢?
2.線性篩質數表
線性篩是一種常用的質數表處理方式。
預處理質數表最容易想到的方式是列舉所有範圍內的數,再用上面的方法判斷是否為質數,
時間複雜度最少為O(sqrt(n)*n)
這太大了…
於是乎有了線性篩。
為了明白線性篩是怎麼來的
我們先來看看最簡單的篩法
從小到大列舉1到n的每一個數,
並用這個數篩掉所有所有它的倍數
比如列舉到2,就篩掉4,6,8,10,12…
並給這些數打上不是質數的標記
下面是程式碼
#define N 100000
char notprime[N];
int prime[N],tot;
int init()
{
notprime[1]=1;
for(int i = 2; i <= n; i ++)
{
if(!notprime[i])
{
prime[++tot]=i;
}
int j = 2;
while(j*i<=n)
{
notprime[i*j]=1;
j++;
}
}
}
由調和級數可知,時間複雜度約為O(nlogn)
但是我們的目標是線性!!!
所以我們只需要列舉已經求出的素數表用於篩就可以了
另外,如果被列舉的素數是當前I的因子,同樣可以跳過
因為已經被篩過一遍了
下面給出程式碼
#define N 100000
char notprime[N];
int prime[N],tot,n;
void init()
{
notprime[1]=1;
for(int i = 2; i <= n; i ++)
{
if(!notprime[i])
{
prime[++tot]=i;
}
for(int j = 1; j <= tot&&prime[j]*i<=n; j ++)
{
notprime[prime[j]*i]=1;
if(i%prime[j]==0)break;
}
}
}
至此完成線性時間複雜度的演算法
接下來,我們來學習尤拉函式!!!!
3.尤拉的邪惡函式
什麼是尤拉函式?
尤拉函式是一種重要的數論函式,用希臘字母φ表示。討論的是對於自然數n,小於n且與n互質的數的個數。
至於什麼是互質,用gcd(a,b)表示a,b兩數的最大公約數,gcd(a,b)=1則表示兩數互質
(當然在尤拉函式中是不包括1的)
尤拉函式的樸素求法:
其中pi表示x的質因子
由此得出樸素尤拉函式的求法
int phi(int x)
{
int ans = x;
for(int i = 2; i*i <= x; i++)
if(x%i==0)
{
ans = ans/i*(i-1);
while(x%i==0)
{
x/=i;
}
}
if(x>1)ans=ans/x*(x-1);
return ans;
}
時間複雜度o(sqrt(n))
尤拉函式有很多鬼畜的特點
當p為質數時,φ§=p-1(顯然 )
尤拉函式是不完全積性函式
在gcd(m,n)=1時,φ(mn)=φ(m)φ(n)
特別地,在p為質數時,若gcd(m,p)=1,φ(mp)=φ(m)(p-1)
在p為質數時,若gcd(m,p)=p,φ(mp)=p*φ(m)
利用這些性質,我們可以線上性篩素數的同時完成線性篩尤拉函式
#define N 100006
char notprime[N];
int phi[N],prime[N],tot,n;
void init()
{
notprime[1]=1;
phi[1]=1;
for(int i = 2; i <= n;i++)
{
if(!notprime[i])
{
phi[i]=i-1;
prime[++tot]=i;
}
for(int j = 1; j <= tot && prime[j]*i<= n; j++)
{
notprime[prime[j]*i]=1;
if(i%prime[j]==0)
{
phi[prime[j]*i]=prime[j]*phi[i];
break;
}
else phi[prime[j]*i]=phi[i]*(prime[j]-1);
}
}
}
至此我們以O(n)複雜度完成了預處理了尤拉函式的值
現在我們來說說尤拉函式可以做什麼
4.例題
洛谷P2158 儀仗隊
題目描述
作為體育委員,C君負責這次運動會儀仗隊的訓練。儀仗隊是由學生組成的N * N的方陣,為了保證隊伍在行進中整齊劃一,C君會跟在儀仗隊的左後方,根據其視線所及的學生人數來判斷隊伍是否整齊(如下圖)。 現在,C君希望你告訴他隊伍整齊時能看到的學生人數。
輸入輸出格式
輸入格式:
共一個數N
輸出格式:
共一個數,即C君應看到的學生人數。
連結:https://www.luogu.org/problemnew/show/P2158
經過分析,每多出一排,所增加的視線數目為φ(n)*2
因此我們套用模板求出他們的和就可以了
注意n=1時特判
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <queue>
using namespace std;
#define N 40005
bool notprime[N];
int prime[N],phi[N],tot,n;
void init()
{
notprime[1]=1;
phi[1]=1;
for(int i = 2; i <= n; i ++)
{
if(!notprime[i])
{
prime[++tot]=i;phi[i]=i-1;
}
for(int j = 1;j<=tot&&prime[j]*i<=n;j++)
{
notprime[prime[j]*i]=1;
if(i%prime[j]==0)
{
phi[i*prime[j]]=phi[i]*prime[j];
break;
}
else phi[i*prime[j]]=phi[i]*(prime[j]-1);
}
}
}
int main()
{
scanf("%d",&n);
init();
long long ans = 1;
for(int i = 1; i <= n; i ++)
{
ans += phi[i-1]+phi[i-1];
}
if(n==1)ans--;
printf("%lld",ans);
}