1. 程式人生 > >數論模板(1) 質數判斷、線性篩、樸素尤拉函式線性篩

數論模板(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的)
尤拉函式的樸素求法:

666
其中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);
}