尤拉篩,線性篩,洛谷P2158儀仗隊
首先我們先把題目分析一下。
emmmm,這應該是一個找規律,應該可以打表,然後我們再分析一下圖片,發現如果這個點可以被看到,那它的橫座標和縱座標應該互質,而互質的條件就是它的橫座標和縱座標的最大公約數為一,那這題的意思就變成了,在一個n * n的方格內尋找所有點的橫座標和縱座標互質的點的個數。
但是這樣複雜度肯定是過不去的。打表時間花費也是很多的,所以我們需要找到加快速度的方法,就是用尤拉函式來加快速度,所以我們就要實現大的優化,我們先明確尤拉函式是個什麼東西.
尤拉函式
\(φ(x)\)表示在\(1\)到\(x - 1\)中所有與x互質的數的個數。這個函式一般就叫做尤拉函式。這個函式還具有一些性質.
如果\(x\)是質數,那\(φ(x)=x-1\)。(質數的性質就是這樣)
- 若\(m\)與\(n\)互質,那\(φ(n * m)= φ(n)* φ(m)\).(可以根據乘法原理推出)
當\(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\)時,為啥就要退出呢
原理:
首先我們需要明白一些性質:
我們篩素數時應該從小到大篩,方便後面優化時間,所以原始碼迴圈中i。
任何一個合數都可以表示為幾個質數的乘積。所以我們想要線上性時間內篩素數的話,每個合數應該都只被它的最小質因子篩去。
當\(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\)小(因為是早篩到的)。
因此時間複雜度是線性的。
尤拉篩
尤拉篩其實跟線性篩差不了多少。
首先我們應該熟記尤拉函式的性質。並且這種篩法還可以進行線性求積性函式。
原理
- 我們看線性篩的第二個性質,尤拉函式是不是也滿足,因此防止多餘的運算
程式碼
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
}
}