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 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)
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的时候…