W
e
l
c
o
m
e
: )

[学习笔记] 质数与唯一分解定理 - 数论

素性测试

素性测试就是判断某个数是否为质数。

费马素性测试

费马小定理 内容:若 \(p\) 为质数,\(a\)为任意整数,有 \(a^{p-1}\equiv 1(mod\ p)\)

那么可以多次随机取一个基数 \(a\in (1,p)\)\(p\) 满足上式,那么它为质数的可能性就越大。称 \(p\) 是基于 \(a\) 的伪素数。

但对于Carmichael数(561、1105、1729…),不管取什么 \(a\) 的值总能够通过测试。但Carmicheal数的分布极为稀疏,所以还是个不错的选择。但若追求更高的准确性,就要依靠下面的方法。

Millar Rabin 素性测试

结合了费马小定理和二次探测原理,详情见OI WIKI。

inline ll qpow(ll a, ll n, ll mod){
	ll ans = 1;
	while(n){
		if(n & 1) ans = (__int128)a * ans % mod;
		a = (__int128)a * a % mod;
		n >>= 1;
	} return ans;
}
inline bool MillarRabin(ll n){
	if(n < 3 || n%2 == 0) return n == 2;
	ll u = n-1, t = 0;
	while(!(u%2)) u >>= 1, ++t;
	for(int i=1; i<=8; ++i){
		ll a = rand()%(n-2) + 2, v = qpow(a, u, n), s;
		if(v == 1) continue;
		for(s=0; s<t; ++s){
			if(v == n-1) break;
			v = qpow(v, 2, n);
		}
		if(s == t) return false;
	} return true;
}

素数筛

素数筛一般用来找到1~n内所有的质数。

埃氏筛


欧拉筛

欧拉筛是一种线性筛,每个数只能被他的最小质因子筛去,所以每个数只被筛了一遍,所以复杂度\(O(n)\)


唯一分解定理(算数基本定理)

任何一个大于1的整数n都可以分解成若干个素因数的连乘积\(n=p_1^{k1}p_2^{k2}p_3^{k3}p_4^{k4}...\)

这条定理看似简单实则非常厉害,可以吧许多复杂的数论问题转化到质因子层面上。

约数个数定理

任意一个大于1的整数 \(n\) 的因数和都可以表示为 \(\prod {ki+1}\)。证明很简单,对所有质因子的系数排列组合+乘法原理即可。

约数和定理

枚举正整数 \(n\) 的全部因子,每个素因子 \(p\) 都有 \(p^0\)\(p^1\)\(p^2\)……,\(p^k\)。总共 \(k+1\) 种情况。利用乘法原理,一个正整数 \(n\) 的约数和即为 \(\prod \sum_{j=0}^{k}p_i^j =(p_1^0+p_1^1+…+p_1^{k_1})(p_2^0+p_2^1+…+p_2^{k_2})…\)

约数积定理

同样枚举每个因子,每个素因子都有 \(p_i^0 \times p_i^1 \times p_i^2 \times … \times p_i^{k_i}=p_i^{\frac{k_i(k_i+1)}{2}}\) 种情况。根据乘法原理,约数积即为 \(\begin{split} prod=(p_1p_2p_3…p_i)^{\frac{k_1k_2k_3…k_i(k_1+1)(k_2+1)…(k_i+1)}{2^i}} \end{split}\)

分解质因数

试除法

就是从1到 \(\sqrt n\) 的质数全部试一遍,能整除的就是它的质因子。对于小数据或者是已知质因子数量较小时还是比较实用的。

Pollard_Rho

该算法是一种启发式的算法,适用于超大数的质因数分解。详情见OI WIKI

inline ll qpow(ll a, ll n, ll mod){
	ll ans = 1;
	while(n){
		if(n & 1) ans = (__int128)ans * a % mod;
		a = (__int128)a * a % mod;
		n >>= 1;
	} return ans;
}
inline bool millarrabin(ll n){
	if(n < 3 || n % 2 == 0) return n == 2;
	ll u = n - 1, t = 0;
	while(u % 2 == 0) u /= 2, ++t;
	for(int i=0; i<8; ++i){
		ll a = rand() % (n-2) + 2, v = qpow(a, u, n);
		if(v == 1) continue;
		ll s;
		for(s = 0; s < t; ++s){
			if(v == n - 1) break;
			v = qpow(v, 2, n);
		}
		if(s == t) return false;
	}
	return true;
}
inline ll pollardrho(ll n){
	ll s = 0, t = 0;
	ll c = rand() % (n-1) + 1;
	ll step = 0, goal = 1;
	ll val = 1;
	for(goal = 1;; goal<<=1, s = t, val = 1){
		for(step=1; step <= goal; ++step){
			t = (qpow(t, 2, n) + c) % n;
			val = (__int128)val * llabs(t - s) % n;
			if((step % 127) == 0){
				ll d = __gcd(val, n);
				if(d > 1) return d;
			}
		}
		ll d = __gcd(val, n);
		if(d > 1) return d;
	}
}
inline void findfac(ll n){
	if(millarrabin(n)){
		factor[cnt++] = n;
		return;
	}
	ll p = n;
	while(p >= n) p = pollardrho(p);
	while(!(n%p)) n /= p;
	findfac(p), findfac(n);
}

例题

阶乘分解

不难发现:\(n!\) 的质因数即为从1到n所有的质数,可以使用欧拉筛,但问题在于如何求质因数的系数。通过找规律(找了我一个多小时👀️)可以发现,某个质因子 \(p\) 的系数 )可以发现,某个质因子 \(p\) 的系数 \(k=\sum_{i=1}^{\left \lfloor log_p{n} \right \rfloor } \left \lfloor \frac{n}{p^i} \right \rfloor\),最后利用循环求解即可。

#include<bits/stdc++.h>
using namespace std;
int n, prime[80000], cnt;
bitset<1000010> vis;
int main(){
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	cin>>n;
	for(int i=2; i<=n; ++i){
		if(!vis[i]){
			prime[++cnt] = i;
			int t = 1, cn = 0;
			for(int k=1; k<=log(n)/log(i); ++k){
				t *= i;
				cn += n/t;
			}
			cout<<i<<' '<<cn<<'\n';
		}
		for(int j=1; j<=cnt; ++j){
			if(prime[j]*i > n) break;
			vis[prime[j]*i] = 1;
			if(i % prime[j] == 0) break;
		}
	} return 0;
}

[Violet 5]樱花

这道题主要是推狮子,先来把分母去掉:

\[\begin{split} \frac{1}{x}+ \frac{1}{y}&=\frac{1}{n!}\\ \frac{x+y}{xy}&=\frac{1}{n!}\\ n!(x+y)&=xy \end{split} \]

接着因式分解:

\[\begin{split} (n!)^2&=(n!)^2-n!(x+y)+xy\\ &=(n!-x)(n!-y)\\ &=(x-n!)(y-n!) \end{split} \]

由上式易知,解的数量即为 \((n!)^2\) 的因子数。
经过"阶乘分解"那道题之后我们可以轻松求出 \(n!\) 的质因子和指数,平方操作就是把指数乘2。最后统计因子个数即为所求。

#include<bits/stdc++.h>
using namespace std;
const int m = 1e9 + 7;
int n, pr[80000], cnt, ans = 1;
bitset<1000010> vis;
int main(){
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	cin>>n;
	for(int i=2; i<=n; ++i){
		if(!vis[i]){
			pr[cnt++] = i;
			int cn = 0, lg = log(n)/log(i), t = 1;
			for(int j=1; j<=lg; ++j) cn += n/(t*=i);
			ans = (long long)ans * ((cn<<1) | 1) % m;
		}
		for(int j=0; j<cnt; ++j){
			if(pr[j]*i > n) break;
			vis[pr[j]*i] = 1;
			if(i % pr[j] == 0) break;
		}
	} return cout<<ans, 0;
}

Sumdiv

由约数和定理可知,约数和为 \(Sumdiv=\prod \sum_{j=0}^{k}p_i^j\)。但直接一项一项求的话复杂度太高,要不起。所以考虑等比数列求和。

等比数列求和

等比数列即为每相邻两项之比都相等的数列,并且他们的比叫做公比。下面推导求和公式。

首先在左右两边同乘初始项:

\[\begin{split} S&=\sum_{i=0}^{n}ra^i\\ aS&=a\sum_{i=0}^{n}ra^i\\ aS&=\sum_{i=1}^{n+1}ra^i\\ \end{split} \]

然后提出来 \(a^{n+1}\) 并加上 \(a^0\) 项得:

\[\begin{split} aS&=\sum_{i=1}^{n+1}ra^i\\ aS&=\sum_{i=0}^{n}ra^i+ra^{n+1}-ra^0\\ aS&=S+ra^{n+1}-r \end{split} \]

移项并化系数为一得:

\[\begin{split} aS-S&=ra^{n+1}-r\\ S(a-1)&=ra^{n+1}-r\\ S&=\frac{ra^{n+1}-r}{a-1} \end{split} \]

注意,在 \(a=1\) 时有 \(S=(n+1)r\)


回到题目,狮子就可以写成 $Sumdiv=\prod \frac{p^{k+1}-1}{p-1}\ mod\ 9907 $。

计算时,\(p^{k+1}\) 用快速幂求出,\((p-1)^{-1}\) 用逆求解。可用费马小定理,也可用扩展欧几里得。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll a, b, m = 9901, ans = 1;
map<ll ,bool> vis;
inline ll qpow(ll a, ll n, ll mod){ //快速幂
	ll ans = 1;
	while(n){
		if(n & 1) ans = (__int128)ans * a % mod;
		a = (__int128)a * a % mod;
		n >>= 1;
	}return ans;
}
inline bool mr(ll k){ // Millar Rabin 素性测试
	if(k < 3 || k%2 == 0) return k == 2;
	ll u = k-1, t = 0;
	while(!(u%2)) u >>= 1, ++t;
	for(int i=1; i<=8; ++i){
		ll a = rand()%(k-2)+2, v = qpow(a, u, k), s;
		if(v == 1) continue;
		for(s=0; s<t; ++s){
			if(v == k-1) break;
			v = qpow(v, 2, k);
		}
		if(s == t) return false;
	} return true;
}
inline ll pr(ll k){ // pollard_rho 分解质因数
	ll i = 0, t = 1, c = rand()%(k-1)+1, x = rand()%k;
	ll y = x;
	while(++i){
		x = (qpow(x, 2, k) + c) % k;
		ll d = __gcd(llabs(x-y), k);
		if(d != 1 && d != k) return d;
		if(y == x) return k;
		if(i == t) y = x, t <<= 1;
	}
}
inline void findfac(ll n){
	if(n < 2) return;
	if(mr(n)){
		if(vis[n]) return;
		vis[n] = 1;
		ll s = a, cn = 0;
		ll inv = qpow(n-1, m-2, m); //费马小定理求逆
		while(!(s%n)) s /= n, ++cn;
		ans *= (__int128)(qpow(n, cn*b+1, m)-1) * inv % m;
		return;
	}
	ll p = n;
	while(p >= n) p = pr(p);
	while(!(n%p)) n /= p;
	findfac(n), findfac(p);
}
signed main(){
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	cin>>a>>b;
	findfac(a);
	return cout<<(ans%m + m)%m, 0; //重新取模变为最小正整数
}
posted @ 2024-05-02 19:12  XiaoLe_MC  阅读(61)  评论(0编辑  收藏  举报