1. 程式人生 > >尤拉篩,線性篩,洛谷P2158儀仗隊

尤拉篩,線性篩,洛谷P2158儀仗隊

題目

首先我們先把題目分析一下。

emmmm,這應該是一個找規律,應該可以打表,然後我們再分析一下圖片,發現如果這個點可以被看到,那它的橫座標和縱座標應該互質,而互質的條件就是它的橫座標和縱座標的最大公約數為一,那這題的意思就變成了,在一個n * n的方格內尋找所有點的橫座標和縱座標互質的點的個數。

但是這樣複雜度肯定是過不去的。打表時間花費也是很多的,所以我們需要找到加快速度的方法,就是用尤拉函式來加快速度,所以我們就要實現大的優化,我們先明確尤拉函式是個什麼東西.

尤拉函式

\(φ(x)\)表示在\(1\)\(x - 1\)中所有與x互質的數的個數。這個函式一般就叫做尤拉函式。這個函式還具有一些性質.

  1. 如果\(x\)是質數,那\(φ(x)=x-1\)。(質數的性質就是這樣)

  2. \(m\)\(n\)互質,那\(φ(n * m)= φ(n)* φ(m)\).(可以根據乘法原理推出)
  3. \(x\)是質數時,那\(φ (x^k)=(x-1)×x^{k-1}\)

所以我們可以通過觀察和推算髮現,如果想求出結果,那就是對於圖的每個橫座標,都記錄他的尤拉函式的值,然後加起來就是最終結果。然後把結果乘2,因為縱座標也需要進行一波這樣的操作。最後再加上一(因為2,2這個點也算)

那到底應該怎麼篩使得尤拉函式能夠很快的算出來呢。

線性篩和尤拉篩

這裡我們就要引進兩個演算法——線性篩和尤拉篩。

線性篩是指以線性的時間篩素數,尤拉篩是以線性的時間求出尤拉函式。

而且他們之間有著異曲同工之妙。

線性篩

程式碼:

#include <iostream>
#include <cstdio>
#include <algorithm>
#define "maxn" "100010" 
int prime[maxn];//表示第幾個質數。
bool vis[maxn] = {1, 1};//判斷是否為質數,如果是則為0
int main()
{
    int tot = 0;
    int n;
    scanf("%d", &n);
    for(int i = 2; i <= n; i++)
    {
        if(!vis[i])
            prime[++tot] = i;
        for(int j = 0; j < tot; j++)
        {
            if(prime[j] * i >= n || i % prime[j] == 0)
                break;
            vis[i * prime[j]] = 1;
        }
    }
}

我們分析上面的程式碼,唯一可能難理解的地方就是\(break\)的那個判斷了。這也是尤拉篩的精髓所在,如果已經超出了範圍需要退出,這個自不必多說,但是,如果\(i\%prime[j]==0\)時,為啥就要退出呢

原理:

首先我們需要明白一些性質:

  1. 我們篩素數時應該從小到大篩,方便後面優化時間,所以原始碼迴圈中i。

  2. 任何一個合數都可以表示為幾個質數的乘積。所以我們想要線上性時間內篩素數的話,每個合數應該都被它的最小質因子篩去。

  3. \(i\%prime[j]==0\)時,則\(i\)\(prime[j]\)的倍數,設\(i\)\(prime[j]*n\),如果繼續向下篩的話,下一個要篩的數是\(i*prime[j+1]=prime[j]*n*prime[j]\);因為此時要篩的數即\(i*prime[j+1]\)一定就已經被\(prime[j]\)篩去了,因為根據性質1,\(prime[j]\)要比\(i\)小(因為是早篩到的)。

    因此時間複雜度是線性的。

尤拉篩

尤拉篩其實跟線性篩差不了多少。
首先我們應該熟記尤拉函式的性質。並且這種篩法還可以進行線性求積性函式。

原理

  1. 我們看線性篩的第二個性質,尤拉函式是不是也滿足,因此防止多餘的運算

程式碼

    memset(isprime, 1, sizeof(isprime));
    isprime[1] = false;
    for (int i = 2; i <= listsize; i++)
    {
        if (isprime[i])
        {
             prime[++primesize]  =i;
             phi[i] = i - 1;//性質1
        }
        for (int j = 1; j <= primesize && i * prime[j] <= listsize; j++)
        {
            isprime[i * prime[j]] = false;
            if (i % prime[j] == 0)//說明他們之間不互質,且i是prime[j]的倍數,就可以用性質3.
            {
                phi[i * prime[j]] = phi[i] * prime[j];
                break;
            }
            phi[i * prime[j]] = phi[i] * (prime[j] - 1)//即phi[j];因為他們互質,所以可用性質1,2
        }
    }