1.欧拉函数
φ(n):1~n内,与n互质的数的个数
φ(6)=2,1 2 3 4 5 6
任意正整数n:
n
=
p
1
a
1
p
2
a
2
.
.
.
p
m
a
m
n=p^{a_1}_1p^{a_2}_2...p^{a_m}_m
n=p1a1p2a2...pmam (
p
i
p_i
pi 为素数)
欧拉函数计算公式:
φ
(
n
)
=
n
(
1
−
1
p
1
)
(
1
−
1
p
2
)
.
.
.
(
1
−
1
p
m
)
φ(n)=n(1-\frac{1}{p_1})(1-\frac{1}{p_2})...(1-\frac{1}{p_m})
φ(n)=n(1−p11)(1−p21)...(1−pm1)
证明:(容斥原理)
1.从1~n
中去掉
p
1
、
p
2
、
.
.
.
、
p
m
p_1、p_2、...、p_m
p1、p2、...、pm的所有倍数(剩余个数
N
1
=
n
−
n
p
1
−
n
p
2
−
.
.
.
−
n
p
m
N_1=n-\frac{n}{p_1}-\frac{n}{p_2}-...-\frac{n}{p_m}
N1=n−p1n−p2n−...−pmn)
2.加上所有
p
i
∗
p
j
p_i*p_j
pi∗pj的倍数(
N
2
=
N
1
+
n
p
1
∗
p
2
+
n
p
1
∗
p
3
+
.
.
.
+
n
p
2
∗
p
3
+
.
.
.
n
p
n
−
1
∗
p
n
N_2=N_1+\frac{n}{p_1*p_2}+\frac{n}{p_1*p3}+...+\frac{n}{p_2*p_3}+...\frac{n}{p_{n-1}*p_n}
N2=N1+p1∗p2n+p1∗p3n+...+p2∗p3n+...pn−1∗pnn)
3.减去所有
p
i
∗
p
j
∗
p
k
p_i*p_j*p_k
pi∗pj∗pk的倍数(
N
3
=
N
2
−
n
p
1
∗
p
2
∗
p
3
−
.
.
.
N_3=N_2-\frac{n}{p_1*p_2*p_3-...}
N3=N2−p1∗p2∗p3−...n)
…
进行算术处理后,就会知道
(
1
−
1
p
1
)
(
1
−
1
p
2
)
.
.
.
(
1
−
1
p
m
)
(1-\frac{1}{p_1})(1-\frac{1}{p_2})...(1-\frac{1}{p_m})
(1−p11)(1−p21)...(1−pm1)展开后就会是上述容斥原理所要求的式子。
时间复杂度分析:
通过上式直接进行计算,算法的时间复杂度瓶颈在于分解质因数
O(√n)。
例题
输入
3
3
6
8
输出
2
2
4
代码
O ( n a i ) O(n\sqrt a_i) O(nai) ——> 4 ∗ 1 0 6 4*10^6 4∗106~ 5 ∗ 1 0 6 5*10^6 5∗106
#include <iostream>
#include <algorithm>
#include <unordered_map>
#include <vector>
const int maxn=2010;
const int mod=1e9+7;
using namespace std;
int phi(int x)
{
int ans=x;
for(int i=2;i<=x/i;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;
}
int main()
{
int n;
cin>>n;
while(n--)
{
int x;
cin>>x;
cout<<phi(x)<<endl;
}
return 0;
}
2.筛法求欧拉函数
O(n)
如果我们要求出1~n中每一个数的欧拉函数,如果我们还是用公式去算,那么我们一个个去分解质因数就很慢,整个程序时间复杂度就是O(n√n),所以要用筛法。
例题
primse[j]是否在i中出现过
只跟质因子有关,跟质因子出现的次数无关
i%primes[j]==0
φ
(
i
)
=
i
(
1
−
1
p
1
)
(
1
−
1
p
2
)
.
.
.
(
1
−
1
p
k
)
.
.
.
(
1
−
1
p
k
)
φ(i)=i(1-\frac{1}{p_1})(1-\frac{1}{p_2})...(1-\frac{1}{p_k})...(1-\frac{1}{p_k})
φ(i)=i(1−p11)(1−p21)...(1−pk1)...(1−pk1) pj出现过
φ
(
p
j
∗
i
)
=
p
j
∗
i
∗
(
1
−
1
p
1
)
(
1
−
1
p
2
)
.
.
.
(
1
−
1
p
k
)
.
.
.
(
1
−
1
p
k
)
=
p
j
∗
φ
(
i
)
φ(p_j*i)=p_j*i*(1-\frac{1}{p_1})(1-\frac{1}{p_2})...(1-\frac{1}{p_k})...(1-\frac{1}{p_k})=p_j*φ(i)
φ(pj∗i)=pj∗i∗(1−p11)(1−p21)...(1−pk1)...(1−pk1)=pj∗φ(i)
i%primes[j]!=0
φ
(
i
)
=
i
(
1
−
1
p
1
)
(
1
−
1
p
2
)
.
.
.
(
1
−
1
p
k
)
φ(i)=i(1-\frac{1}{p_1})(1-\frac{1}{p_2})...(1-\frac{1}{p_k})
φ(i)=i(1−p11)(1−p21)...(1−pk1)
φ
(
p
j
∗
i
)
=
p
j
∗
i
∗
(
1
−
1
p
1
)
(
1
−
1
p
2
)
.
.
.
(
1
−
1
p
k
)
(
1
−
1
p
j
)
=
p
j
∗
φ
(
i
)
∗
(
1
−
1
p
j
)
=
φ
(
i
)
∗
(
p
j
−
1
)
φ(p_j*i)=p_j*i*(1-\frac{1}{p_1})(1-\frac{1}{p_2})...(1-\frac{1}{p_k})(1-\frac{1}{p_j})=p_j*φ(i)*(1-\frac{1}{p_j})=φ(i)*(p_j-1)
φ(pj∗i)=pj∗i∗(1−p11)(1−p21)...(1−pk1)(1−pj1)=pj∗φ(i)∗(1−pj1)=φ(i)∗(pj−1)
#include <iostream>
#include <algorithm>
#include <unordered_map>
#include <vector>
const int maxn=1e6+5;
const int mod=1e9+7;
using namespace std;
int primes[maxn],euler[maxn],cnt;
bool st[maxn];
void get_eulers(int n)
{
euler[1]=1;
for(int i=2;i<=n;i++)
{
if(!st[i])
{
primes[cnt++]=i;
euler[i]=i-1;//质数的欧拉函数除掉它本身
}
for(int j=0;primes[j]<=n/i;j++)
{
int k=primes[j]*i;
st[k]=true;
if(i%primes[j]==0)
{
euler[k]=euler[i]*primes[j];
break;
}
euler[k]=euler[i]*(primes[j]-1);
}
}
}
int main()
{
int n;
cin>>n;
get_eulers(n);
long long sum=0;
for(int i=1;i<=n;i++)
sum+=euler[i];
cout<<sum<<endl;
return 0;
}
3.欧拉定理
若a和n互质,则有
a
φ
(
n
)
≡
1
m
o
d
n
a^{φ(n)}≡1\ mod\ n
aφ(n)≡1 mod n
eg. a=5,n=6,则φ(6)=2,
a
φ
(
n
)
=
25
a^{φ(n)}=25
aφ(n)=25,25%6=1,1%6=1
简要证明
假设1~n中,与n互质 的数有
a
1
,
a
2
,
.
.
.
a
φ
(
n
)
a_1,a_2,...a_{φ(n)}
a1,a2,...aφ(n)
因为a和n互质,提出a,
a
φ
(
n
)
(
a
1
,
a
2
,
.
.
.
a
φ
(
n
)
)
≡
a
1
,
a
2
,
.
.
.
a
φ
(
n
)
(
m
o
d
n
)
a^{φ(n)}(a_1,a_2,...a_{φ(n)})≡a_1,a_2,...a_{φ(n)}\ (mod\ n)
aφ(n)(a1,a2,...aφ(n))≡a1,a2,...aφ(n) (mod n)
——> a φ ( n ) ≡ 1 ( m o d n ) a^{φ(n)}≡1\ (mod\ n) aφ(n)≡1 (mod n)
所以 a a 1 , a a 2 , . . . a a φ ( n ) aa_1,aa_2,...aa_{φ(n)} aa1,aa2,...aaφ(n)在mod n的情况下和上述 a 1 , a 2 , . . . a φ ( n ) a_1,a_2,...a_{φ(n)} a1,a2,...aφ(n)在mod n的情况下是相等的
4.费马小定理
若p为质数,且由欧拉定理有
a
φ
(
p
)
≡
1
m
o
d
p
a^{φ(p)}≡1\ mod\ p
aφ(p)≡1 mod p,
φ
(
p
)
=
p
−
1
φ(p)=p-1
φ(p)=p−1=>费马定理
:
若正整数a与素数p互质,则有 a p − 1 ≡ 1 ( m o d p ) a^{p-1}≡1(mod\ p) ap−1≡1(mod p),或写为 a p ≡ a ( m o d p ) a^p≡a(mod\ p) ap≡a(mod p)
费马小定理求逆元
若p为素数,且(a,p)=1, a p − 1 ≡ 1 ( m o d p ) a^{p-1}≡1(mod\ p) ap−1≡1(mod p) → a a p − 2 ≡ 1 ( m o d p ) aa^{p-2}≡1(mod\ p) aap−2≡1(mod p),a的逆元就是 a p − 2 a^{p-2} ap−2
欧拉定理求逆元
若a,p互素, a φ ( p ) ≡ 1 ( m o d p ) a^{φ(p)}≡1(mod\ p) aφ(p)≡1(mod p) → a a φ ( p ) − 1 ≡ 1 ( m o d p ) aa^{φ(p)-1}≡1(mod\ p) aaφ(p)−1≡1(mod p)
5.快速幂
例题
计算范围可达到10^9
ak,朴素做法循环一遍,时间复杂度O(k)
int ans=1;
for(int i=1;i<=k;i++)
{
ans=ans*a%p;
}
如果用快速幂的话,logk->30左右
算法原理:
预处理出 a 2 0 m o d p a^{2^0}\ mod\ p a20 mod p, a 2 1 m o d p a^{2^1}\ mod\ p a21 mod p,…, a 2 l o g k m o d p a^{2^{logk}}\ mod\ p a2logk mod p这logk个值
a k = a 2 i ∗ a 2 j ∗ a 2 m . . . = a 2 i + 2 j + 2 m + . . . a^k=a^{2^i}*a^{2^j}*a^{2^m...}=a^{2^i+2^j+2^m+...} ak=a2i∗a2j∗a2m...=a2i+2j+2m+...,就是要把指数拆成二进制表示。
a
2
0
a^{2^0}
a20 ——>
a
1
=
a
a^1=a
a1=a
a
2
1
a^{2^1}
a21 ——>
=
a
2
0
∗
2
=
(
a
2
0
)
2
=a^{2^0*2=(a^{2^0})^2}
=a20∗2=(a20)2
a
2
2
a^{2^2}
a22 ——>
=
a
2
1
∗
2
=
(
a
2
1
)
2
=a^{2^1*2=(a^{2^1})^2}
=a21∗2=(a21)2
…
a
2
l
o
g
k
a^{2^{logk}}
a2logk ——>
a
2
l
o
g
k
−
1
∗
2
=
(
a
2
l
o
g
k
−
1
)
2
a^{2^{logk-1}*2=(a^{2^{logk-1}})^2}
a2logk−1∗2=(a2logk−1)2
eg. 4 5 m o d 10 4^5\ mod\ 10 45 mod 10
4
2
0
%
10
=
4
4^{2^0}\%\ 10=4
420% 10=4
4
2
1
%
10
=
6
4^{2^1}\%\ 10=6
421% 10=6
4
2
2
%
10
=
6
4^{2^2}\%\ 10=6
422% 10=6
4 5 = 4 ( 101 ) 2 = 4 2 0 + 2 2 = 4 2 0 ∗ 4 2 2 = ( 4 ∗ 6 ) % 10 = 4 ( % 10 ) 4^5=4^{(101)_2}=4^{2^0+2^2}=4^{2^0}*4^{2^2}=(4*6)\%\ 10=4\ (\%10) 45=4(101)2=420+22=420∗422=(4∗6)% 10=4 (%10)
时间复杂度分析:
预处理部分logk个数,拆分成的二进制表示这步也是logk个,所以总的时间复杂度就是O(logk)
#include <iostream>
#include <algorithm>
#include <unordered_map>
#include <vector>
const int maxn=1e6+5;
const int mod=1e9+7;
typedef long long ll;
using namespace std;
long long quick_pow(int a,int b,int p)
{
long long res=1%p;//一定要注意要这样初始化,因为当p=1时,结果就会不同了,1%1=0
while(b)
{
if(b&1)//当前位数是1,就乘到结果上
res=res*1ll*a%p;//第一次就是a^{2^0}
a=a*1ll*a%p;//平方一下
b>>=1;//右移一位
}
return res;
}
int main()
{
int n;
scanf("%d",&n);
while(n--)//数据量有点大,建议用scanf
{
int a,k,p;
scanf("%d%d%d",&a,&k,&p);
printf("%lld\n",quick_pow(a,k,p));
}
return 0;
}
5.快速幂求逆元
对于整数a,n,如果有gcd(a,n)=1,那么就存在一个整数x使得 ax=1(mod n),x即为a的逆元
性质
a b ≡ a ∗ b − 1 ( m o d m ) \frac{a}{b}≡a*b^{-1}(mod\ m) ba≡a∗b−1(mod m)
b ∗ a b ≡ b ∗ a ∗ b − 1 ( m o d m ) b*\frac{a}{b}≡b*a*b^{-1}(mod\ m) b∗ba≡b∗a∗b−1(mod m)
a ≡ b ∗ a ∗ b − 1 ( m o d m ) a≡b*a*b^{-1}(mod\ m) a≡b∗a∗b−1(mod m),又a和m互质,所以有
b ∗ b − 1 ≡ 1 ( m o d m ) b*b^{-1}≡1(mod\ m) b∗b−1≡1(mod m)
eg. b=3,m=5,求b%m的逆元 2
(3*2)%5=1
逆元为 3 5 − 2 = 3 3 = 27 % 5 = 2 3^{5-2}=3^3=27\%5=2 35−2=33=27%5=2
例题
输入
3
4 3
8 5
6 3
输出
1
2
impossible
费马小定理求逆元
若p为素数,且a,p互质, a p − 1 ≡ 1 ( m o d p ) a^{p-1}≡1(mod\ p) ap−1≡1(mod p) → a a p − 2 ≡ 1 ( m o d p ) aa^{p-2}≡1(mod\ p) aap−2≡1(mod p),a的逆元就是 a p − 2 a^{p-2} ap−2
#include <iostream>
#include <algorithm>
#include <unordered_map>
#include <vector>
const int maxn=1e6+5;
const int mod=1e9+7;
typedef long long ll;
using namespace std;
ll quick_pow(int a,int b,int p)
{
ll res=1%p;//一定要注意要这样初始化,因为当p=1时,结果就会不同了,1%1=0
while(b)
{
if(b&1)
res=res*1ll*a%p;//第一次就是a^{2^0}
a=a*1ll*a%p;//平方一下
b>>=1;//右移一位
}
return res;
}
int main()
{
int n;
scanf("%d",&n);
while(n--)//数据量有点大,建议用scanf
{
int a,p;
scanf("%d%d",&a,&p);
if(a%p!=0)
printf("%lld\n",quick_pow(a,p-2,p));
else
printf("impossible\n");
}
return 0;
}
a是p的倍数的时候是无解的
a*x%p=0,不可能模p等于1
由于a不是p的倍数,且p是质数,所以a和p一定互质,由费马定理 b p − 1 % p ≡ 1 b^{p-1}\%p≡1 bp−1%p≡1知逆元一定存在。
int ans=quick_pow(a,p-2,p);
if(ans)
printf("%lld\n",quick_pow(a,p-2,p));
else
printf("impossible\n");
不能这样判断,因为输入数据为2 2,2%2=0逆元应该不存在,但是这里2^0=1,好像没有次幂会为0吧,或许当底数为0的时候…