[学习笔记] 质数与唯一分解定理 - 数论
素性测试
素性测试就是判断某个数是否为质数。
费马素性测试
费马小定理 内容:若 为质数,为任意整数,有
那么可以多次随机取一个基数 若 满足上式,那么它为质数的可能性就越大。称 是基于 的伪素数。
但对于Carmichael数(561、1105、1729…),不管取什么 的值总能够通过测试。但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内所有的质数。
埃氏筛
欧拉筛
欧拉筛是一种线性筛,每个数只能被他的最小质因子筛去,所以每个数只被筛了一遍,所以复杂度。
唯一分解定理(算数基本定理)
任何一个大于1的整数n都可以分解成若干个素因数的连乘积。
这条定理看似简单实则非常厉害,可以吧许多复杂的数论问题转化到质因子层面上。
约数个数定理
任意一个大于1的整数 的因数和都可以表示为 。证明很简单,对所有质因子的系数排列组合+乘法原理即可。
约数和定理
枚举正整数 的全部因子,每个素因子 都有 ,,……,。总共 种情况。利用乘法原理,一个正整数 的约数和即为
约数积定理
同样枚举每个因子,每个素因子都有 种情况。根据乘法原理,约数积即为
分解质因数
试除法
就是从1到 的质数全部试一遍,能整除的就是它的质因子。对于小数据或者是已知质因子数量较小时还是比较实用的。
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); }
例题
阶乘分解
不难发现: 的质因数即为从1到n所有的质数,可以使用欧拉筛,但问题在于如何求质因数的系数。通过找规律(找了我一个多小时👀️)可以发现,某个质因子 的系数 )可以发现,某个质因子 的系数 ,最后利用循环求解即可。
#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]樱花
这道题主要是推狮子,先来把分母去掉:
接着因式分解:
由上式易知,解的数量即为 的因子数。
经过"阶乘分解"那道题之后我们可以轻松求出 的质因子和指数,平方操作就是把指数乘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
由约数和定理可知,约数和为 。但直接一项一项求的话复杂度太高,要不起。所以考虑等比数列求和。
等比数列求和
等比数列即为每相邻两项之比都相等的数列,并且他们的比叫做公比。下面推导求和公式。
首先在左右两边同乘初始项:
然后提出来 并加上 项得:
移项并化系数为一得:
注意,在 时有
回到题目,狮子就可以写成 。
计算时, 用快速幂求出, 用逆求解。可用费马小定理,也可用扩展欧几里得。
#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; //重新取模变为最小正整数 }
本文作者:XiaoLe_MC
本文链接:https://www.cnblogs.com/xiaolemc/p/18170453
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步