稍微进阶一些,比昨天难(除了类欧)
欧拉函数 φ ( n ) \varphi(n) φ(n)表示与n互质的正整数个数
显然,当 p p p为质数时, φ ( p ) = p − 1 \varphi(p)=p-1 φ(p)=p−1
给一个定理: φ ( p a ) = p a − p a − 1 \varphi(p^a)=p^a-p^{a-1} φ(pa)=pa−pa−1
证明:
显然 gcd ( x , p a ) = 1 \gcd(x,p^a)=1 gcd(x,pa)=1可以推出 gcd ( x , p ) = 1 \gcd(x,p)=1 gcd(x,p)=1
在 [ 1 , p a ] [1,p^a] [1,pa]中,只有 gcd ( p , k p ) ≠ 1 , k ∈ [ 1 , p a − 1 ] \gcd(p,kp)≠1,k \in [1,p^{a-1}] gcd(p,kp)̸=1,k∈[1,pa−1]
则在 [ 1 , p a ] [1,p^a] [1,pa]中,有 p a − 1 p^{a-1} pa−1个数和 p a p^a pa不互质
故 φ ( p a ) = p a − p a − 1 \varphi(p^a)=p^a-p^{a-1} φ(pa)=pa−pa−1
大家都知道欧拉函数是积性函数,那怎证明?
除了大力推公式,我提供一种新的方法:
观察下表:
1 m + 1 2 m + 1 . . . ( n − 1 ) m + 1 2 m + 2 2 m + 2 . . . ( n − 1 ) m + 2 . . . . . . . . . . . . r m + r 2 m + r . . . ( n − 1 ) m + r . . . . . . . . . . . . m 2 m 3 m . . . n m \begin{matrix} 1&m+1&2m+1&...&(n-1)m+1\\ 2&m+2&2m+2&...&(n-1)m+2\\ ...&...&...&&...\\ r&m+r&2m+r&...&(n-1)m+r\\ ...&...&...&&...\\ m&2m&3m&...&nm\\ \end{matrix} 12...r...mm+1m+2...m+r...2m2m+12m+2...2m+r...3m............(n−1)m+1(n−1)m+2...(n−1)m+r...nm
若 gcd ( r , m ) = d > 1 \gcd(r,m)=d>1 gcd(r,m)=d>1,则第 r r r行与 m n mn mn没有互质的元素(显然)
这是因为第 r r r行元素形式为 k m + r km+r km+r,而 d ∣ m , d ∣ r d|m,d|r d∣m,d∣r,所以 d ∣ ( k m + r ) d|(km+r) d∣(km+r)
若 gcd ( r , m ) = 1 \gcd(r,m)=1 gcd(r,m)=1,则第 r r r行元素都与 m m m互质
假设 k 2 m + r ≡ k 2 m + r ( m o d n ) k_2m+r ≡ k_2m+r(mod \ n) k2m+r≡k2m+r(mod n),则 ( k 1 − k 2 ) m ≡ 0 ( m o d n ) (k_1-k_2)m ≡ 0(mod \ n) (k1−k2)m≡0(mod n),得到 k 1 = k 2 k_1=k_2 k1=k2
所以第 r r r行元素模 n n n两两不同余,故 φ ( n ) \varphi(n) φ(n)个数与 n n n互质
满足 gcd ( r , m ) = 1 \gcd(r,m)=1 gcd(r,m)=1的行号 r r r有 φ ( m ) \varphi(m) φ(m)个,故表中与nm互质的数有 φ ( n ) φ ( m ) \varphi(n)\varphi(m) φ(n)φ(m)个
即 φ ( n m ) = φ ( n ) φ ( m ) \varphi(nm)=\varphi(n)\varphi(m) φ(nm)=φ(n)φ(m)
证明了欧拉函数的积性,加上 φ ( p a ) = p a − p a − 1 \varphi(p^a)=p^a-p^{a-1} φ(pa)=pa−pa−1( p p p为素数)
设n的质因子分解式为 ∏ p i q i \prod_{}{p_i}^{q_i} ∏piqi,则 φ ( n ) = n ∏ p i − 1 p i \varphi(n)=n\prod_{}\frac{p_i-1}{p_i} φ(n)=n∏pipi−1
证明:
φ ( n ) = φ ( ∏ p i q i ) = ∏ φ ( p i q i ) = ∏ ( p i q i ) ( 1 − 1 p i ) = n ∏ ( 1 − 1 p i ) = n ∏ p i − 1 p i \varphi(n)=\varphi(\prod_{}{p_i}^{q_i})=\prod_{}\varphi({p_i}^{q_i})=\prod_{}(p_i^{q_i})(1-\frac{1}{p_i})=n\prod_{}(1-\frac{1}{p_i})=n\prod_{}\frac{p_i-1}{p_i} φ(n)=φ(∏piqi)=∏φ(piqi)=∏(piqi)(1−pi1)=n∏(1−pi1)=n∏pipi−1
欧拉函数还有一个重要的性质:
n = ∑ d ∣ n φ ( d ) n=\sum\limits_{d|n}\varphi(d) n=d∣n∑φ(d)
证明:
设 f ( n ) = ∑ d ∣ n φ ( d ) f(n)=\sum\limits_{d|n}\varphi(d) f(n)=d∣n∑φ(d)
则 f ( p i q i ) = ∑ e = 0 q i φ ( p i e ) = 1 + p i − 1 + . . . + p i q i = p i q i − 1 = p i q i f({p_i}^{q_i})=\sum\limits_{e=0}^{q_i}\varphi({p_i}^e)=1+p_i-1+...+{p_i}^{q_i}={p_i}^{q_i-1}={p_i}^{q_i} f(piqi)=e=0∑qiφ(pie)=1+pi−1+...+piqi=piqi−1=piqi
因为 φ \varphi φ是积性函数,所以 f f f也是积性函数
所以 f ( n ) = f ( ∏ p i q i ) = ∏ p i q i = n f(n)=f(\prod_{}{p_i}^{q_i})=\prod_{}{p_i}^{q_i}=n f(n)=f(∏piqi)=∏piqi=n
线性筛积性函数
线性筛可以筛积性函数
对于积性函数 f ( x ) f(x) f(x),我们可以使用线性筛快速预处理
具体操作一般是:
线性筛筛欧拉函数
根据上述规则,我们能够清晰的知道流程:
上面的2,3如何说明?
大概就是对于 i ∗ p r i m e [ j ] i*prime[j] i∗prime[j],如果 p r i m e j prime_j primej是第一次出现,那么他就会产生 i i i个数字和他不互质。如果已经出现,那么就什么也不用动,因为相当于它没有用
代码:
void sieve() {
clr(f) ; cnt = 0 ; phi[1] = 1 ;
rep(i, 2, N) {
if (!f[i]) {
prime[++cnt] = i ;
phi[i] = i - 1 ; // 素数
}
for (int j = 1; j <= cnt && prime[j] * i <= N; j++) {
f[i * prime[j]] = 1 ;
if (i % prime[j]) phi[i * prime[j]] = phi[i] * (prime[j] - 1) ;
else {
phi[i * prime[j]] = phi[i] * prime[j] ;
break ;
}
}
}
}
之后说一说欧拉函数在OI中的应用(多题预警)
然后是一道讲烂的题目 [SDOI2008]仪仗队
我们从0开始标号
显然,能够被看到的学生的坐标应该满足 gcd ( x , y ) = 1 \gcd(x,y)=1 gcd(x,y)=1
于是问题就是求 2 + ∑ i = 1 n − 1 ∑ j = 1 n − 1 [ gcd ( i , j ) = 1 ] = 3 + 2 ∑ i = 1 n ∑ j = 1 i − 1 [ gcd ( i , j ) = 1 ] = 3 + 2 ∑ i = 2 n φ ( i ) = 1 + 2 ∑ i = 1 n φ ( i ) 2+\sum\limits_{i=1}^{n-1}\sum\limits_{j=1}^{n-1}[\gcd(i,j)=1]=3+2\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{i-1}[\gcd(i,j)=1]=3+2\sum\limits_{i=2}^{n}\varphi(i)=1+2\sum\limits_{i=1}^{n}\varphi(i) 2+i=1∑n−1j=1∑n−1[gcd(i,j)=1]=3+2i=1∑nj=1∑i−1[gcd(i,j)=1]=3+2i=2∑nφ(i)=1+2i=1∑nφ(i)
注意要特判一下 n = 1 n=1 n=1的情况
int f[N], prime[N], phi[N] ;
int n, cnt ;
ll ans ;
void sieve() {
clr(f) ;
phi[1] = 1 ;
rep(i, 2, n) {
if (!f[i]) {
prime[++cnt] = i ;
phi[i] = i - 1 ;
}
for (int j = 1; j <= cnt && i * prime[j] <= n; j++) {
f[i * prime[j]] = 1 ;
if (i % prime[j]) phi[i * prime[j]] = phi[i] * (prime[j] - 1) ;
else {
phi[i * prime[j]] = phi[i] * prime[j] ;
break ;
}
}
}
}
signed main(){
scanf("%d", &n) ;
if (n == 1) print(0) ;
sieve() ;
rep(i, 1, n - 1) ans += 2 * phi[i] ;
printf("%lld\n", ans + 1) ;
return 0 ;
}
之后是 [SDOI2012]Longge的问题
求 ∑ i = 1 n gcd ( i , n ) \sum\limits_{i=1}^n\gcd(i,n) i=1∑ngcd(i,n)
这类问题常见的操作就是枚举贡献,然后再通过除以权值转化成我们能够解决的形式
我们假设枚举 k k k表示某些 gcd ( i , n ) \gcd(i,n) gcd(i,n)的值为k,那我们只需要知道一共有多少个
推一下式子:
∑ i = 1 n gcd ( i , n ) = ∑ k ∣ n k ∑ i = 1 n [ gcd ( i , n ) = k ] = ∑ k ∣ n k ∑ i = 1 n k [ gcd ( i , n k ) = 1 ] = ∑ k ∣ n k φ ( n k ) \sum\limits_{i=1}^n\gcd(i,n)=\sum\limits_{k|n}k\sum\limits_{i=1}^n[\gcd(i,n)=k]=\sum\limits_{k|n}k\sum\limits_{i=1}^{\frac{n}{k}}[\gcd(i,\frac{n}{k})=1]=\sum\limits_{k|n}k\varphi(\frac{n}{k}) i=1∑ngcd(i,n)=k∣n∑ki=1∑n[gcd(i,n)=k]=k∣n∑ki=1∑kn[gcd(i,kn)=1]=k∣n∑kφ(kn)
求解欧拉函数直接通过定义去推导,时间复杂度 O ( n ) O(\sqrt n) O(n),总时间复杂度 O ( n ) O(n) O(n)
被两个细节坑了:有可能是平方数要特判;n要开ll
int n ;
ll ans ;
int calc(int x) { // 计算φ(x)
int res = x ;
for (int i = 2; i * i <= x; i++)
if (x % i == 0) {
res = res / i * (i - 1) ;
while (x % i == 0) x /= i ;
}
if (x > 1) res = res / x * (x - 1) ;
return res ;
}
signed main(){
scanf("%lld", &n) ;
for (int i = 1; i * i <= n; i++)
if (n % i == 0) {
ans += i * calc(n / i) ;
if (i != n / i) ans += (n / i) * calc(i) ;
}
printf("%lld\n", ans) ;
return 0 ;
}
之后是一个类似的题目,可以先不看下面想一想 GCD
还是上一题的套路,我们枚举贡献
∑ i = 1 n ∑ j = 1 n [ gcd ( i , j ) ∈ p r i m e ] = ∑ k = 1 n [ k ∈ p r i m e ] ∑ i = 1 n ∑ j = 1 n [ gcd ( i , j ) = k ] = ∑ k = 1 n [ k ∈ p r i m e ] ∑ i = 1 ⌊ n k ⌋ ∑ j = 1 ⌊ n k ⌋ [ gcd ( i , j ) = 1 ] = ∑ k = 1 n [ k ∈ p r i m e ] ( 2 ∗ s u m [ ⌊ n k ⌋ ] − 1 ) \sum\limits_{i=1}^n\sum\limits_{j=1}^n[\gcd(i,j) \in prime]=\sum\limits_{k=1}^n[k \in prime]\sum\limits_{i=1}^n\sum\limits_{j=1}^n[\gcd(i,j)=k]=\sum\limits_{k=1}^n[k \in prime]\sum\limits_{i=1}^{\left\lfloor \frac{n}{k}\right\rfloor}\sum\limits_{j=1}^{\left\lfloor \frac{n}{k}\right\rfloor}[\gcd(i,j)=1]=\sum\limits_{k=1}^n[k \in prime](2*sum[{\left\lfloor \frac{n}{k}\right\rfloor}]-1) i=1∑nj=1∑n[gcd(i,j)∈prime]=k=1∑n[k∈prime]i=1∑nj=1∑n[gcd(i,j)=k]=k=1∑n[k∈prime]i=1∑⌊kn⌋j=1∑⌊kn⌋[gcd(i,j)=1]=k=1∑n[k∈prime](2∗sum[⌊kn⌋]−1)
其中 s u m sum sum用前缀和统计欧拉函数值
int n, cnt ;
int phi[N], prime[N], f[N] ;
ll sum[N], ans ;
void sieve(){
clr(f) ; cnt = 0 ;
phi[1] = 1 ;
for (int i = 2; i <= n; i++) {
if (!f[i]) {
prime[++cnt] = i ;
phi[i] = i - 1 ;
}
for (int j = 1; j <= cnt && i * prime[j] <= n; j++) {
f[prime[j] * i] = 1 ;
if (i % prime[j]) phi[i * prime[j]] = phi[i] * (prime[j] - 1) ;
else {
phi[i * prime[j]] = phi[i] * prime[j] ;
break ;
}
}
}
}
signed main(){
scanf("%d", &n) ;
sieve() ;
for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + phi[i] ;
rep(i, 1, cnt) ans += 2 * sum[(int)ceil(1ll * n / prime[i])] ;
printf("%lld\n", ans - cnt) ;
return 0 ;
}
之后是一个毒瘤题 [SDOI2008]沙拉公主的困惑
简化问题,其实就是让你求 ∑ i = 1 n ! [ gcd ( i , m ! ) = 1 ] \sum\limits_{i=1}^{n!}[\gcd(i,m!)=1] i=1∑n![gcd(i,m!)=1], m ≤ n m \le n m≤n
很明显,我们发现这个式子能够转化成 a n s = n ! m ! φ ( m ! ) ans=\frac{n!}{m!}\varphi(m!) ans=m!n!φ(m!)
继续化简: a n s = n ! m ! m ! ∏ p ∣ m p − 1 p = n ! ∏ ( p − 1 ) ∏ p ans=\frac{n!}{m!}m!\prod\limits_{p|m}\frac{p-1}{p}=n!\frac{\prod_{}(p-1)}{\prod_{}p} ans=m!n!m!p∣m∏pp−1=n!∏p∏(p−1),p为质数且 p < = m p<=m p<=m
注意,不能直接处理阶乘,逆元,因为有可能会有消掉的
正解应该是对 n > = R n>=R n>=R的 n ! n! n!消去一个 R R R,对 m > = R m>=R m>=R的 ∏ p \prod_{}p ∏p同时消去一个 R R R才对
具体实现看代码
int f[N], prime[N / 10], inv[N], phi[N / 10], sum[N / 10], fac[N], pos[N] ;
int T, MOD, n, m, cnt ;
void init() {
f[0] = f[1] = 1 ;
rep(i, 2, N - 10) {
if (!f[i]) prime[++cnt] = i ;
for (int j = 1; j <= cnt && prime[j] * i <= N - 10; j++) {
f[prime[j] * i] = 1 ;
if (i % prime[j] == 0) break ;
}
}
inv[1] = 1 ;
for (int i = 2; i < MOD && i <= N - 10; i++)
inv[i] = 1ll * (MOD - MOD / i) * inv[MOD % i] % MOD ;
phi[0] = 1 ;
rep(i, 1, cnt) phi[i] = 1ll * phi[i - 1] * (prime[i] - 1) % MOD ;
sum[0] = 1 ;
rep(i, 1, cnt)
if (prime[i] != MOD) sum[i] = 1ll * sum[i - 1] * inv[prime[i] % MOD] % MOD ;
else sum[i] = sum[i - 1] ;
fac[0] = 1 ;
rep(i, 1, N - 10)
if (i != MOD) fac[i] = 1ll * fac[i - 1] * i % MOD ;
else fac[i] = fac[i - 1] ;
rep(i, 2, N - 10)
if (f[i]) pos[i] = pos[i - 1] ;
else pos[i] = pos[i - 1] + 1 ;
}
signed main() {
scanf("%d%d", &T, &MOD) ;
init() ;
while (T--){
scanf("%d%d", &n, &m) ;
if(n >= MOD && m < MOD) puts("0") ;
else printf("%d\n", 1ll * fac[n] * phi[pos[m]] % MOD * sum[pos[m]] % MOD) ;
}
return 0 ;
}
之后是 HAOI2012 火星人
给定一个数N,问你这个数取多少次欧拉函数后变成1
用代数表示就是 φ ( φ ( . . . φ ( N ) ) ) = 1 \varphi(\varphi(...\varphi(N)))=1 φ(φ(...φ(N)))=1
我们发现,要操作为1,必须在最后使得N变成2
根据 φ ( ∏ i = 1 m p i q i ) = ∏ i = 1 m ( p i − 1 ) ∗ p i q i − 1 \varphi(\prod\limits_{i=1}^m{p_i}^{q_i})=\prod\limits_{i=1}^m(p_i-1)*{p_i}^{q_i-1} φ(i=1∏mpiqi)=i=1∏m(pi−1)∗piqi−1,我们没次操作会把上次操作的答案中的一个因子2变成1,所以,求操作过程会产多少个因子2就好了
对于 2 n 2^n 2n,显然操作次数为 n n n
若一开始一个是一个质数,我们第一次操作不会讲其中的因子 2 2 2变成 1 1 1,所以答案要加 1 1 1
哦对,还有线性筛筛的不是欧拉函数值而是操作次数
int T, n, ans = 1, cnt = 0 ;
int phi[N], prime[N], f[N] ;
void sieve() {
phi[1] = 1 ;
rep(i, 2, N - 10) {
if (!f[i]) prime[++cnt] = i, phi[i] = phi[i - 1] ;
for (int j = 1; j <= cnt && prime[j] * i <= N - 10; j++) {
f[prime[j] * i] = 1 ;
phi[i * prime[j]] = phi[i] + phi[prime[j]] ;
if (i % prime[j] == 0) break ;
}
}
}
signed main(){
sieve() ;
scanf("%d", &T) ;
while (T--){
scanf("%d", &n) ;
rep(i, 1, n) {
int p, q ;
scanf("%d%d", &p, &q) ;
if (p == 2) ans-- ;
ans += phi[p] * q ;
}
printf("%lld\n", ans) ;
ans = 1 ;
}
return 0 ;
}
欧拉函数就讲这么多吧,毕竟已经有5个题了
首先是欧拉定理
若 gcd ( a , p ) = 1 , 则 a φ ( p ) ≡ 1 ( m o d p ) \gcd(a,p)=1,则a^{\varphi(p)}≡1(mod \ p) gcd(a,p)=1,则aφ(p)≡1(mod p)
为何要满足 gcd ( a , p ) = 1 \gcd(a,p)=1 gcd(a,p)=1?
转化同余方程为丢番图方程 a φ ( p ) + b p = 1 a^{\varphi(p)}+bp=1 aφ(p)+bp=1
在数论初步中我们已经推过这个方程有解的必要条件是 gcd ( a , p ) = 1 \gcd(a,p)=1 gcd(a,p)=1
根据定理,我们可以发现一些方便的性质:
a b ≡ a k φ ( p ) a b % φ ( p ) ≡ a b % φ ( p ) ( m o d p ) a^b≡a^{k\varphi(p)}a^{b\%\varphi(p)}≡a^{b\%\varphi(p)}(mod \ p) ab≡akφ(p)ab%φ(p)≡ab%φ(p)(mod p)
扩展欧拉定理: 若 b > φ ( p ) , a b ≡ a b % φ ( p ) + φ ( p ) ( m o d p ) b>\varphi(p),a^b≡a^{b\%\varphi(p)+\varphi(p)}(mod \ p) b>φ(p),ab≡ab%φ(p)+φ(p)(mod p)
给一个例题练练手
上帝与集合的正确用法
求 2 2 2 . . . % p 2^{2^{2^{...}}}\%p 222...%p的值:
设 p p p为形如 2 k ∗ q 2^k*q 2k∗q的数, q q q为奇数
则: a n s = 2 k ( 2 2 2 . . . − k m o d q ) = 2 k ( 2 ( 2 2 . . . − k ) % φ ( q ) m o d q ) ans=2^k(2^{2^{2^{...}}-k} mod \ q)=2^k(2^{(2^{2^{...}}-k)\%\varphi(q)} mod \ q) ans=2k(222...−kmod q)=2k(2(22...−k)%φ(q)mod q)
总有一天 φ ( q ) = 1 \varphi(q)=1 φ(q)=1,则就可以算出答案,反着退回去就行了
程序用递归实现
int phi[N], prime[N], f[N] ;
int n, T, cnt ;
void sieve() {
f[0] = f[1] = 1 ; phi[1] = 1 ;
rep(i, 2, N - 10) {
if (!f[i]) prime[++cnt] = i, phi[i] = i - 1 ;
for (int j = 1; j <= cnt && prime[j] * i <= N - 10; j++) {
f[i * prime[j]] = 1 ;
if (i % prime[j]) phi[i * prime[j]] = phi[i] * (prime[j] - 1) ;
else {
phi[i * prime[j]] = phi[i] * prime[j] ;
break ;
}
}
}
}
int mul(int a, int b, int MOD) {
int res = 0 ;
for (; b; b >>= 1, a = (a + a) % MOD) if (b & 1) res = (res + a) % MOD ;
return res ;
}
int pw(int a, int b, int MOD) {
int res = 1 ;
for(; b; b >>= 1, a = mul(a, a, MOD)) if (b & 1) res = mul(res, a, MOD) ;
return res ;
}
int work(int x) {
return x == 1 ? 0 : pw(2, work(phi[x]) + phi[x], x) ;
}
signed main(){
sieve() ;
scanf("%d", &T) ;
while (T--) {
scanf("%d", &n) ;
printf("%d\n", work(n)) ;
}
return 0 ;
}
费马小定理:
当p为质数时, a p − 1 ≡ 1 ( m o d p ) a^{p-1}≡1(mod \ p) ap−1≡1(mod p)
将 φ ( p ) = p − 1 \varphi(p)=p-1 φ(p)=p−1代入欧拉定理即可
常用于求解逆元
威尔逊定理
( p − 1 ) ! ≡ − 1 ( m o d p ) (p-1)!≡-1(mod \ p) (p−1)!≡−1(mod p)
证明:
当 p = 2 p=2 p=2时显然成立
当 p > 2 p>2 p>2时,根据逆元的唯一性, 2 ( p − 2 ) 2~(p-2) 2 (p−2)两两配对互为逆元
故结论成立
因为都是随机算法所以一起讲了
首先,我们可以通过费马小定理测试是否是质数
我们可以找一些 a a a一一验证,判断 p p p是否为素数
但是总有一些强伪素数能够通过测试,为提高测试的准确率,我们引入了一个叫做二次探测定理的玩野
对于某素数 p p p,则 ∀ x ∈ ( 0 , p ) , x 2 ≡ 1 ( m o d p ) \forall x \in (0,p),x^2≡1(mod \ p) ∀x∈(0,p),x2≡1(mod p)的解总为 ± 1 ±1 ±1
证明很简单,直接平方差就是了
对于每个测试是否是素数的数 a a a,我们可以把 a p − 1 a^{p-1} ap−1不断开方,如果开放的结果为 − 1 -1 −1,则p通过测试,否则继续
具体实现上可能有一些不一样,代码上有详细注释。
bool miller(ll p, int a) {
// 判定p是否是质数,用a^(p-1)≡1(mod p) 和二次检测判定
if (p == 2) return 1 ;
if (p % 2 == 0 || p == 1) return 0 ; // 奇
ll d = p - 1 ; while (d % 2 == 0) d /= 2 ; // 把能开方的部分开掉
// 根据二次检测,判定 (x=a^d)是否同余1(mod p)
int m = pw(a, d, p) ;
if (m == 1) return 1 ; // 出现1,通过二次检测
for (; d - 1 <= p; d *= 2, m = 1ll * m * m % p) if (m == p - 1) return 1 ;
// 出现-1,通过二次检测
return 0 ; // 合数
}
rep(i, 0, 8) {
if (x == prime[i]) { // x 是被判定的数
puts("Prime") ;
break ;
}
if (!miller(x, prime[i])) {
puts("Not prime.") ;
break ;
}
}
之后是Pollard-Rho算法
设有一个大合数,我们要求他的素因数分解式
假设 n = p q n=pq n=pq,我们要求 p p p和 q q q,怎么做?
先看一个暴力方法: I ′ m f e l l i n g l u c k y a l g o r i t h m \mathfrak{I'm \ felling \ lucky \ algorithm} I′m felling lucky algorithm
每次随机一个 [ 2 , n ] [2,n] [2,n],然后进行判断,可惜成功的概率比中奖还低。。。
我们要提到一种简单而且十分有用的提高效率的技巧,叫做 B i r t h d a y T r i c k \mathfrak{Birthday \ Trick} Birthday Trick
举个例子来说明这个小 T r i c k \mathfrak{Trick} Trick:
从 [ 1 , 1000 ] [1,1000] [1,1000]中选一个数 i i i,使得 P ( i = 100 ) = 1 1000 P(i=100)=\frac{1}{1000} P(i=100)=10001
从 [ 1 , 1000 ] [1,1000] [1,1000]中选一个数 i , j i,j i,j,使得 P ( a b s ( i − j ) = 100 ) = 1 500 P(abs(i-j)=100)=\frac{1}{500} P(abs(i−j)=100)=5001
发现成功概率增长了1倍!!!
那么我们选出 x 1 , x 2 , . . . , x k x_1,x_2,...,x_k x1,x2,...,xk,满足 a b s ( x i − x j ) = 100 abs(x_i-x_j)=100 abs(xi−xj)=100的概率又是多少呢?
用数据说话:
我们发现,当 k = 30 k=30 k=30的时候,成功的概率已经接近一半,而 k = 100 k=100 k=100时,几乎确保成功
这就叫 生日悖论 \color{white}\colorbox{black}{生日悖论} 生日悖论
我们尝试通过生日悖论来优化 I ′ m f e l l i n g l u c k y a l g o r i t h m \mathfrak{I'm \ felling \ lucky \ algorithm} I′m felling lucky algorithm
我们把不再选一个数,而是选k个数,并判断 ( x i − x j ) ∣ n (x_i-x_j)|n (xi−xj)∣n
取 k = n k=\sqrt n k=n,就可以吧可行性提高到 50 % 50\% 50%
因此,对于一个10位数,我们只需要选取 k = 1 0 5 k=10^5 k=105个随机数而不是 1 0 10 10^{10} 1010
不幸的是,我们并没有节省我们的开销,我们仍要做 k 2 = 1 0 10 k^2=10^{10} k2=1010次比较和除法
但是幸运的是,我们有更优秀的办法
我们可以选出 k k k个数 x 1 , x 2 , . . . , x k x_1,x_2,...,x_k x1,x2,...,xk,判断是否存在 gcd ( a b s ( x i − x j ) , n ) > 1 \gcd(abs(x_i-x_j),n)>1 gcd(abs(xi−xj),n)>1的情况
若存在,则 gcd ( a b s ( x i − x j ) , n ) \gcd(abs(x_i-x_j),n) gcd(abs(xi−xj),n)就是 n n n的一个因子
但我们要选取的数是很多的,不能存在内存中
我们不生成 k k k个数进行比较,而是一个一个生成并检查连续的两个数
f ( x ) = ( x 2 + c ) % n f(x)=(x^2+c)\%n f(x)=(x2+c)%n来生成伪随机数(c输一个钦定随机的数)
并不是所有函数都可以这样做,根据前辈的经验这个函数可以
我们从 x 1 = 2 x_1=2 x1=2开始, x n + 1 = f ( x n ) x_{n+1}=f(x_n) xn+1=f(xn)
对于大部分的数据这个算法能够正常运行
但是对于某些毒瘤数据,TA将会进入无限循环,这个环就是算法名称的由来
遇到环的时候,算法就是用了,需要改变 c c c的值
我们如何检测环的存在?
这是 F l o y d Floyd Floyd 发明的算法:
假设我们在一个很长的圆形轨道上行走,我们如何知道已经走完了一圈呢?
让 A A A 和 B B B 按照 B B B 的速度是 A A A 的速度两倍的设定从同一起点开始往前走
当 B B B 第一次追上 A A A 时,我们知道, A A A 已经走了一圈了
这种方法称为 F l o y d Floyd Floyd 周期检测策略
代码锅了不发了
定义:若 gcd ( a , p ) = 1 \gcd(a,p)=1 gcd(a,p)=1,存在正整数 x x x使得 a x ≡ 1 ( m o d p ) a^x≡1(mod \ p) ax≡1(mod p),则称最小的 x x x称为 a a a模 P P P的次数,记做 O r d p a {Ord_p}^a Ordpa
定理:若正整数 n n n满足 a n ≡ 1 ( m o d p ) a^n≡1(mod \ p) an≡1(mod p),则 x = O r d p a x={Ord_p}^a x=Ordpa
证明:假设定理不成立,记 x = O r d p a x={Ord_p}^a x=Ordpa
那么必有两正整数 k , r k,r k,r,使得 n = k x + r n=kx+r n=kx+r且 0 < r < x 0<r<x 0<r<x
而 a n = a k x + r = a k x ∗ a r ≡ 1 ( m o d p ) a^n=a^{kx+r}=a^{kx}*a^r≡1(mod \ p) an=akx+r=akx∗ar≡1(mod p)
可以推出 a r ≡ 1 ( m o d p ) a^r≡1(mod \ p) ar≡1(mod p)
这与次数 x x x的定义矛盾
推论: O r d p a ∣ φ ( p ) {Ord_p}^a|\varphi(p) Ordpa∣φ(p)
定理:设 x = O r d p a , 则 1 , a , a 2 , . . . a x − 1 x={Ord_p}^a,则1,a,a^2,...a^{x-1} x=Ordpa,则1,a,a2,...ax−1两两不同余
证明:假设定理不成立
则 a k − j ≡ 1 ( m o d p ) a^{k-j}≡1(mod \ p) ak−j≡1(mod p)
而 0 < k − j < x 0<k-j<x 0<k−j<x
这与次数 x x x的定义矛盾
定义:若 O r d p g = φ ( p ) {Ord_p}^g=\varphi(p) Ordpg=φ(p),则称 g g g为模 P P P意义下的原根
求法: O r d p g = φ ( p ) {Ord_p}^g=\varphi(p) Ordpg=φ(p)的意思就是方程 g x ≡ 1 ( m o d p ) g^x≡1(mod \ p) gx≡1(mod p)的最小正整数解释 φ ( p ) \varphi(p) φ(p)
而 g φ ( p ) ≡ 1 ( m o d p ) g^{\varphi(p)}≡1(mod \ p) gφ(p)≡1(mod p)是一定成立的
那么我们就可以从 2 2 2到 n − 1 n-1 n−1枚举 g g g,检验 g x ≡ 1 ( m o d p ) g^x≡1(mod \ p) gx≡1(mod p) 在区间 1 < x < φ ( p ) 1<x<\varphi(p) 1<x<φ(p)中是恒不成立即可
而满足 g x ≡ 1 ( m o d p ) g^x≡1(mod \ p) gx≡1(mod p)的 x x x必定是 φ ( p ) \varphi(p) φ(p)的约数
所以我们只需要枚举 φ ( p ) \varphi(p) φ(p)的因子即可
模板代码:
int fj[N] ;
int pw(int a, int b, int MOD) {
int res = 0 ;
for (; b; b >>= 1, a = 1ll * a * a % MOD) if (b & 1) res = 1ll * res * a % MOD ;
return res ;
}
int proots(int n) {
int tmp = n - 1, cnt = 0 ;
for (int i = 2; i * i <= n; i++)
if (tmp % i == 0) {
fj[++cnt] = i ;
while (tmp % i == 0) tmp /= i ; // 只需要枚举n-1的因数
}
if (tmp > 1) fj[++cnt] = tmp ;
for (int i = 2; i < n; i++) { // 枚举g
int flag = 1 ;
for (int j = 1; j <= cnt; j++) // 逐一判断φ(p)的因子是否可行即可
if (pw(i, (n - 1) / fj[j], n) == 1) {
flag = 0 ;
break ;
}
if (flag) return i ;
}
}
离散对数问题就是要求解决高次同余方程 a x ≡ b ( m o d p ) a^x≡b(mod \ p) ax≡b(mod p)的 x x x的最小整数解
我们可以用大步小步法(BSGS)解决
我们先讨论 P P P为质数时的问题
算法的主要思想是分块
设 x = A p − B x=A \sqrt p-B x=Ap−B(其中 0 ≤ A , B < p 0 \le A,B < \sqrt p 0≤A,B<p)
d A p − B ≡ b ( m o d p ) ⇒ d A p ≡ b ∗ a B ( m o d p ) d^{A \sqrt p-B}≡b(mod \ p)\Rightarrow d^{A \sqrt p}≡b*a^B(mod \ p) dAp−B≡b(mod p)⇒dAp≡b∗aB(mod p)
我们只需要求出 A A A和 B B B就能够算出 x x x
枚举 B B B的值把右边的式子存入哈希表中,然后枚举 A A A值去哈希表中查找左值是否存在,时间复杂度 O ( p ) O(\sqrt p) O(p)
一道模板题 [SDOI2011]计算器
mii hsh ;
int T, ty, a, b, p, ans ;
void bad() {
puts("Orz, I cannot find x!") ;
}
int gcd(int a, int b) {
return !b ? a : gcd(b, a % b) ;
}
int pw(int a, int b, int MOD) {
int res = 1 ;
for (; b; b >>= 1, a = 1ll * a * a % MOD) if (b & 1) res = 1ll *res * a % MOD ;
return res ;
}
int exgcd(int a, int b, int &x, int &y) {
if (!b) {
x = 1 ; y = 0 ;
return a ;
}
int d = exgcd(b, a % b, y, x) ;
y -= (a / b) * x ;
return d ;
}
int BSGS(int a, int b, int MOD) {
if (b % gcd(a, MOD)) return -1 ;
a %= MOD, b %= MOD, hsh.clear() ;
int m = ceil(sqrt(MOD)) ;
for (int i = 0; i <= m; i++, b = 1ll * b * a % MOD) hsh[b] = i ;
// 先枚举B(i)的值将其插入哈希表
for (int i = 1, j = a = pw(a, m, MOD); i <= m; i++, j = 1ll * j * a % MOD) {
if (hsh.find(j) != hsh.end() && i * m >= hsh[j]) return i * m - hsh[j] ;
} // 再枚举A(i)的值判断是否存在,而且需要保证A*sqrt(p)-B(hsh[j])>=0
return -1 ;
}
void work(int a, int b, int p) {
ans = 0 ;
if (ty == 1) ans = pw(a, b, p) ;
if (ty == 2) { // ax≡b(mod p)->ax+py=b
if (a == 0 && b != 0) bad(), ans = -2 ;
int x, y ;
int d = exgcd(a, p, x, y) ; // ax+py=d(gcd(a,p))
if (b % d) bad(), ans = -2 ;
if (ans != -2) ans = ((b / d * x % p) + p) % (p / d) ;
}
if (ty == 3) ans = BSGS(a, b, p) ;
if (ans == -1) bad() ;
else if (ans != -2) printf("%lld\n", ans) ;
}
signed main(){
scanf("%lld%lld", &T, &ty) ;
while (T--) {
int a, b, p ; scanf("%lld%lld%lld", &a, &b, &p) ;
work(a, b, p) ;
}
return 0 ;
}
那么当P不是质数的时候怎么办?
我们依然将原方程展开:
a x + k p = b ( k ∈ Z ) a^x+kp=b(k \in \Z) ax+kp=b(k∈Z)
设 g = gcd ( a , p ) g=\gcd(a,p) g=gcd(a,p),若 b b b不能整出 g g g,则方程无解
否则可以得到 a g a x − 1 ≡ b g ( m o d p g ) \frac{a}{g}a^{x-1}≡\frac{b}{g}(mod \ \frac{p}{g}) gaax−1≡gb(mod gp)
很明显通过换元我们能够得到一个新的高次同余方程
原方程的解就是该方程的解 + 1 +1 +1
我们不断进行下去总有一天 gcd ( a , p ) = 1 \gcd(a,p)=1 gcd(a,p)=1
那么就转化成了BSGS了
模板题 SP3105 MOD
map <int, int> hsh ;
int gcd(int a, int b) {
return !b ? a : gcd(b, a % b) ;
}
int pw(int a, int b, int MOD) {
int res = 1 ;
for (; b; b >>= 1, a = 1ll * a * a % MOD) if (b & 1) res = 1ll * res * a % MOD ;
return res ;
}
int exBSGS(int a, int b, int p) {
if (b == 1) return 0 ;
int tim = 0, d, k = 1 ;
while ((d = gcd(a, p)) ^ 1) { // 不停地做
if (b % d) return -1 ; // 不整除,无解
b /= d, p /= d, ++tim ; // a^(x-1)≡(b/d) *inv(a/d) (mod p/d)
k = 1ll * k * (a / d) % p ;
if (k == b) return tim ; // 发现 k^x≡b(k)(mod ~)
}
hsh.clear() ;
int m = sqrt(p) + 1, kt = 1 ;
for (int i = 0; i < m; i++) {
hsh[1ll * kt * b % p] = i ;
kt = 1ll * kt * a % p ;
}
k = 1ll * k * kt % p ;
rep(i, 1, m) {
if (hsh.find(k) != hsh.end()) return i * m - hsh[k] + tim ;
k = 1ll * k * kt % p ;
}
return -1 ;
}
signed main(){
int a, b, p ;
while (scanf("%lld%lld%lld", &a, &p, &b) != EOF && a && b && p) { // a^x≡b(mod p)
int ans = exBSGS(a, b, p) ;
if (ans == -1) puts("No Solution") ;
else printf("%lld\n", ans) ;
}
return 0 ;
}
还需填坑:二次剩余