数论进阶总结

稍微进阶一些,比昨天难(除了类欧)

先说说欧拉函数

欧拉函数 φ ( n ) \varphi(n) φ(n)表示与n互质的正整数个数
显然,当 p p p为质数时, φ ( p ) = p − 1 \varphi(p)=p-1 φ(p)=p1

给一个定理: φ ( p a ) = p a − p a − 1 \varphi(p^a)=p^a-p^{a-1} φ(pa)=papa1

证明:
显然 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,pa1]

则在 [ 1 , p a ] [1,p^a] [1,pa]中,有 p a − 1 p^{a-1} pa1个数和 p a p^a pa不互质

φ ( p a ) = p a − p a − 1 \varphi(p^a)=p^a-p^{a-1} φ(pa)=papa1

大家都知道欧拉函数是积性函数,那怎证明?

除了大力推公式,我提供一种新的方法:

观察下表:

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............(n1)m+1(n1)m+2...(n1)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 dm,dr,所以 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+rk2m+r(mod n),则 ( k 1 − k 2 ) m ≡ 0 ( m o d   n ) (k_1-k_2)m ≡ 0(mod \ n) (k1k2)m0(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)=papa1 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)=npipi1

证明:

φ ( 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)(1pi1)=n(1pi1)=npipi1

欧拉函数还有一个重要的性质:

n = ∑ d ∣ n φ ( d ) n=\sum\limits_{d|n}\varphi(d) n=dnφ(d)

证明:

f ( n ) = ∑ d ∣ n φ ( d ) f(n)=\sum\limits_{d|n}\varphi(d) f(n)=dnφ(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=0qiφ(pie)=1+pi1+...+piqi=piqi1=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),我们可以使用线性筛快速预处理
具体操作一般是:

  1. 对于素数 p p p,根据定义式直接求出 f ( p ) f(p) f(p)
  2. i % p r i m e [ j ] ≠ 0 i\%prime[j]≠0 i%prime[j]̸=0,则 f ( i ∗ p r i m e [ j ] ) = f ( i ) ∗ f ( p r i m e [ j ] ) f(i*prime[j])=f(i)*f(prime[j]) f(iprime[j])=f(i)f(prime[j])
  3. i % p r i m e [ j ] = 0 i\%prime[j]=0 i%prime[j]=0,则根据定义推出 f ( i ∗ p r i m e [ j ] ) f(i*prime[j]) f(iprime[j]) f ( i ) f(i) f(i)的关系

线性筛筛欧拉函数

根据上述规则,我们能够清晰的知道流程:

  1. 对于素数 i i i,根据定义式直接求出 φ ( i ) = i − 1 \varphi(i)=i-1 φ(i)=i1
  2. i % p r i m e [ j ] ≠ 0 i\%prime[j]≠0 i%prime[j]̸=0,则 φ ( i ∗ p r i m e [ j ] ) = φ ( i ) ∗ ( p r i m e [ j ] − 1 ) \varphi(i*prime[j])=\varphi(i)*(prime[j]-1) φ(iprime[j])=φ(i)(prime[j]1)
  3. i % p r i m e [ j ] = 0 i\%prime[j]=0 i%prime[j]=0,则 φ ( i ∗ p r i m e [ j ] ) = φ ( i ) ∗ p r i m e [ j ] \varphi(i*prime[j])=\varphi(i)*prime[j] φ(iprime[j])=φ(i)prime[j]

上面的2,3如何说明?

大概就是对于 i ∗ p r i m e [ j ] i*prime[j] iprime[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=1n1j=1n1[gcd(i,j)=1]=3+2i=1nj=1i1[gcd(i,j)=1]=3+2i=2nφ(i)=1+2i=1nφ(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=1ngcd(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=1ngcd(i,n)=knki=1n[gcd(i,n)=k]=knki=1kn[gcd(i,kn)=1]=knkφ(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=1nj=1n[gcd(i,j)prime]=k=1n[kprime]i=1nj=1n[gcd(i,j)=k]=k=1n[kprime]i=1knj=1kn[gcd(i,j)=1]=k=1n[kprime](2sum[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=1n![gcd(i,m!)=1], m ≤ n m \le n mn

很明显,我们发现这个式子能够转化成 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!pmpp1=n!p(p1),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=1mpiqi)=i=1m(pi1)piqi1,我们没次操作会把上次操作的答案中的一个因子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) abakφ(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),abab%φ(p)+φ(p)(mod p)

给一个例题练练手

上帝与集合的正确用法

2 2 2 . . . % p 2^{2^{2^{...}}}\%p 222...%p的值:

p p p为形如 2 k ∗ q 2^k*q 2kq的数, 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) ap11(mod p)

φ ( p ) = p − 1 \varphi(p)=p-1 φ(p)=p1代入欧拉定理即可

常用于求解逆元

威尔逊定理

( p − 1 ) ! ≡ − 1 ( m o d   p ) (p-1)!≡-1(mod \ p) (p1)!1(mod p)

证明:

p = 2 p=2 p=2时显然成立

p > 2 p>2 p>2时,根据逆元的唯一性, 2   ( p − 2 ) 2~(p-2) 2 (p2)两两配对互为逆元

故结论成立

之后是Miller-Rabin素数测试和Pollard-Rho因数分解

因为都是随机算法所以一起讲了

首先,我们可以通过费马小定理测试是否是质数

我们可以找一些 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),x21(mod p)的解总为 ± 1 ±1 ±1

证明很简单,直接平方差就是了

对于每个测试是否是素数的数 a a a,我们可以把 a p − 1 a^{p-1} ap1不断开方,如果开放的结果为 − 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} Im 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 ij,使得 P ( a b s ( i − j ) = 100 ) = 1 500 P(abs(i-j)=100)=\frac{1}{500} P(abs(ij)=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(xixj)=100的概率又是多少呢?

用数据说话:

数论进阶总结_第1张图片我们发现,当 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} Im felling lucky algorithm

我们把不再选一个数,而是选k个数,并判断 ( x i − x j ) ∣ n (x_i-x_j)|n (xixj)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(xixj),n)>1的情况

若存在,则 gcd ⁡ ( a b s ( x i − x j ) , n ) \gcd(abs(x_i-x_j),n) gcd(abs(xixj),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) ax1(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) an1(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=akxar1(mod p)

可以推出 a r ≡ 1 ( m o d   p ) a^r≡1(mod \ p) ar1(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=Ordpa1,a,a2,...ax1两两不同余

证明:假设定理不成立

a k − j ≡ 1 ( m o d   p ) a^{k-j}≡1(mod \ p) akj1(mod p)

0 < k − j < x 0<k-j<x 0<kj<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) gx1(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 n1枚举 g g g,检验 g x ≡ 1 ( m o d   p ) g^x≡1(mod \ p) gx1(mod p) 在区间 1 < x < φ ( p ) 1<x<\varphi(p) 1<x<φ(p)中是恒不成立即可

而满足 g x ≡ 1 ( m o d   p ) g^x≡1(mod \ p) gx1(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 ;
	}
}

之后来讲一下大步小步(BSGS)算法

离散对数问题就是要求解决高次同余方程 a x ≡ b ( m o d   p ) a^x≡b(mod \ p) axb(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 0A,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 Bb(mod p)dAp baB(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(kZ)

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}) gaax1gb(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 ;
}

还需填坑:二次剩余

你可能感兴趣的:((EX)BSGS,欧拉函数,Miller-rabin测试,欧拉定理,费马小定理,威尔逊定理,Pollard-Rho因数分解,次数,原根,二次剩余,算法总结)