Welcome to the Nig|

XiaoLe_MC

园龄:1年2个月粉丝:3关注:9

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

素性测试

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

费马素性测试

费马小定理 内容:若 p 为质数,a为任意整数,有 ap11(mod p)

那么可以多次随机取一个基数 a(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=p1k1p2k2p3k3p4k4...

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

约数个数定理

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

约数和定理

枚举正整数 n 的全部因子,每个素因子 p 都有 p0p1p2……,pk。总共 k+1 种情况。利用乘法原理,一个正整数 n 的约数和即为 j=0kpij=(p10+p11++p1k1)(p20+p21++p2k2)

约数积定理

同样枚举每个因子,每个素因子都有 pi0×pi1×pi2××piki=piki(ki+1)2 种情况。根据乘法原理,约数积即为 prod=(p1p2p3pi)k1k2k3ki(k1+1)(k2+1)(ki+1)2i

分解质因数

试除法

就是从1到 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=i=1logpnnpi,最后利用循环求解即可。

#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]樱花

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

1x+1y=1n!x+yxy=1n!n!(x+y)=xy

接着因式分解:

(n!)2=(n!)2n!(x+y)+xy=(n!x)(n!y)=(xn!)(yn!)

由上式易知,解的数量即为 (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=j=0kpij。但直接一项一项求的话复杂度太高,要不起。所以考虑等比数列求和。

等比数列求和

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

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

S=i=0nraiaS=ai=0nraiaS=i=1n+1rai

然后提出来 an+1 并加上 a0 项得:

aS=i=1n+1raiaS=i=0nrai+ran+1ra0aS=S+ran+1r

移项并化系数为一得:

aSS=ran+1rS(a1)=ran+1rS=ran+1ra1

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


回到题目,狮子就可以写成 Sumdiv=pk+11p1 mod 9907

计算时,pk+1 用快速幂求出,(p1)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; //重新取模变为最小正整数
}

本文作者:XiaoLe_MC

本文链接:https://www.cnblogs.com/xiaolemc/p/18170453

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   XiaoLe_MC  阅读(175)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起