先確認一下入門數論的基本知識點:
最大公約數
快速冪
素數篩
輾轉相除求最大公約數:
int gcd(int a,int b)
{
if(b==0) return a;
gcd(b,a%b);
}
素數篩的模板(線性篩):將2到n之間的整數記錄下來,其中最小的素數是2,將表中2的倍數全部不劃去,表中剩餘的最小的數字爲3,再將3的倍數全劃去,以此類推,就能找到所有的素數。這樣做的時間複雜度只有o(nlog log n)。
int prime[maxn];///用於標記曬是否已經篩過
int pri[maxn];///用於儲存素數
void pre_prime()
{
memset(prime,0,sizeof(prime));
cnt = 0;
prime[0] = prime[1] = 1;
for(int i = 2 ; i < maxn ; i++)
{
if(!prime[i]) pri[cnt++] = i;
for(int j = 0 ; j < cnt && i * pri[j] <= maxn ; j++)
{
prime[i * pri[j]] = 1;
if(i % pri[j] == 0) break;///已經篩過的不要重複篩
}
}
}
快速冪:數論問題經常遇見非常大的數,例如 X ^ 22,通常的思路是採用for循環,但當數很大時運算效率會很低。
採用快速冪算法可以將X^22拆爲X^16 , X^4和X^2。時間複雜度直降至o(log n)。
typedef long long ll;
ll quick_pow(ll x,ll n,ll mod)
{
ll ans=1;
while(n>0)
{
if(n&1)
ans=ans*x%mod;
x=x*x%mod;
n>>=1;
}
return ans;
}
歐拉函數
歐拉函數是少於或等於n的數中與n互質的數的數目。
歐拉函數的性質:它在整數n上的值等於對n進行素因子分解後,所有的素數冪上的歐拉函數之積。
歐拉函數的值 通式:φ(x)=x(1-1/p1)(1-1/p2)(1-1/p3)(1-1/p4)…..(1-1/pn),其中p1, p2……pn爲x的所有質因數,x是不爲0的整數。φ(1)=1(唯一和1互質的數(小於等 於1)就是1本身)。 (注意:每種質因數只一個。比如12=2*2*3,那φ(12)=12*(1-1/2)*(1-1/3)=4)
推論:當n爲奇數時,有φ(2n)=φ(n)。
若n是質數p的k次冪,φ(n)=p^k-p^(k-1)=(p-1)p^(k-1),因爲除了p的倍數外,其他數都跟n互質。
設n爲正整數,以 φ(n)表示不超過n且與n互素的正整數的個數,稱爲n的歐拉函數值,這裏函數φ:N→N,n→φ(n)稱爲歐拉函數。
歐拉函數是積性函數——若m,n互質,φ(mn)=φ(m)φ(n)。
特殊性質:當n爲奇數時,φ(2n)=φ(n), 證明與上述類似。
那麼如果我們想要求出其歐拉函數的值就要先對n進行質因子分解:
如果進行直接進行篩因子的話其時間複雜度爲O(n),代碼如下
int prime(int n)
{
int rea=n;
for(int i=2; i<=n; i++)
if(n%i==0)//第一次找到的必爲素因子
{
rea=rea-rea/i;
do
n/=i;//把該素因子全部約掉
while(n%i==0);//比如12=2*2*3這樣可以將所有的2除去
}
return rea;
}
有沒有方法將其降低複雜度呢?我們知道 “ 由於任何一個合數都至少有一個不大於根號n的素因子 ”,所以只需遍歷到根號n即可
int prime(int n)
{
int rea=n;
for(int i=2; i*i<=n; i++)
if(n%i==0)//第一次找到的必爲素因子
{
rea=rea-rea/i;
do
n/=i;//把該素因子全部約掉
while(n%i==0);
}
if(n>1)
rea=rea-rea/n;//如果到最後n有就將n直接處理
return rea;
}
最後學長說用篩法求歐拉函數很好(可能我太菜了吧,,,,沒有領會到)
primel prime[50000];
int pri[20000];
void prim()
{
memset(prime,0,sizeof(prime));
prime[0]=prime[1]=1;
int k=0;
for(int i=2; i<50000; i++)
{
if(!prime[i])
pri [k++]=i;
for(int j=0; j<k&&i*pri [j]<50000; j++)
{
prime[i*p[j]=1;
if(!(i%pri [j]))
break;
}
}
}//篩選法打表
int phi(int n)
{
int rea=n;
for(int i=0; pri [i]*pri [i]<=n; i++)//對於一些不是素數的可不遍歷
if(n%pri [i]==0)
{
rea=rea-rea/n;
do
n/=pri [i];
while(n%pri [i]==0);
}
if(n>1)
rea=rea-rea/n;
return rea;
}