数论——唯一分解定理

唯一分解定理

  • 前言
  • 一、定理内容
  • 二、素数拆分
    • 1. 试除法
    • 2. Pollard-Rho算法
  • 三、应用
    • 1.因子个数
    • 2.因子和
    • 3.一些例题
  • 参考资料


前言

引理:对所有素数 p 和 所有整数 a,b,如果 p | ab,则 p | a 或 p | b (或两者都成立)
证明:采用反证法。假设 p | ab,但 p ∤ a 且 p ∤ b。故gcd(a,p)= 1,gcd(b,p)= 1,故gcd(ab,p) = 1,而 gcd(ab,p) = p,矛盾,原命题成立。

定理:如果素数 p ∣ a 1 a 2 a 3 . . . a r p | a _1 a_2a_3...a_r pa1a2a3...ar,则 p ∣ a 1 p | a_1 pa1 p ∣ a 2 p | a_2 pa2…或 p ∣ a r p | a_r par至少一个成立

由上面这个定理我们发现:
①数n可以以某种方式分解成素数乘积
②仅有唯一的因数分解(不考虑因数重排)

一、定理内容

任何一个大于1的整数n 都可以分解成若干个素因数的连乘积,如果不计各个素因数的顺序,那么这种分解是惟一的。
n = ∏ i = 1 r p i e i n = \prod_{i=1}^r p_i^{e_i} n=i=1rpiei
e i e_i ei为正整数, p i p_i pi为互不相同的素数。

证明:唯一分解定理

二、素数拆分

给你一个数n,如何将他拆分成素数的乘积呢?

1. 试除法

直接枚举因子然后把当前因子全部除尽即可,时间复杂度 O ( n ) O(\sqrt n) O(n )

int e[N],p[N];
void divide(int n)
{
    int cnt = 0;
    for(int i=2; i*i<=n; i++)
    {
        if(n%i == 0)
        {
            p[++cnt] = i,e[cnt] = 0;
            while(n % i == 0)
            {
                n /= i;
                e[cnt]++;
            }
        }
    }
    if(n > 1)
        p[++cnt] = n,e[cnt]=1;
    for(int i = 1;i <= cnt; ++ i)
        cout << p[i] << "^" << e[i] << endl;
}

2. Pollard-Rho算法

一种大数(如n>1e18)分解的随机算法
有空再细写


三、应用

1.因子个数

将n分解过后, n = p 1 e 1 ∗ p 2 e 2 ∗ . . . ∗ p r e r n=p_1^{e_1}*p_2^{e_2}*...*p_r^{e_r} n=p1e1p2e2...prer ,n的因子一定是 p i p_i pi的组合,且每个组合的选择有 e i + 1 e_i+1 ei+1个,因此因子个数为 ∏ i = 1 r ( e i + 1 ) \prod_{i=1}^r{(e_i+1)} i=1r(ei+1)

例题:
给定x,y(x,y<231 )问xy的因子数mod10007是多少?
如果强行计算,xy已经是天文数字了,所以我们考虑将他分解为素数的乘积
x y = ( p 1 e 1 ∗ p 2 e 2 ∗ . . . ∗ p r e r ) y = p 1 y e 1 ∗ p 2 y e 2 ∗ . . . ∗ p r y e r x^y=(p_1^{e_1}*p_2^{e_2}*...*p_r^{e_r})^y=p_1^{ye_1}*p_2^{ye_2}*...*p_r^{ye_r} xy=(p1e1p2e2...prer)y=p1ye1p2ye2...pryer
答案即为 ∏ i = 1 r ( y e i + 1 ) m o d 10007 \prod_{i=1}^r{(ye_i+1)} mod10007 i=1r(yei+1)mod10007


2.因子和

S ( n ) S(n) S(n)为因子和,考虑每个 p i , S ( n ) = ( p i 0 + p i 1 + . . . + p i e i ) ∗ ( S ( 剩 余 ) ) p_i,S(n)=(p_i^{0}+p_i^{1}+...+p_i^{e_i}) *(S(剩余)) pi,S(n)=(pi0+pi1+...+piei)(S()),由等比数列求和公式得知因子和为 ∏ i = 1 r p i e i + 1 − 1 p i − 1 \prod_{i=1}^r\frac{p_i^{e_i+1}-1}{p_i-1} i=1rpi1piei+11

例题:
约数之和
数论——唯一分解定理_第1张图片
思路:与上题类似,要注意的是分数取模的时候,如果p-1 ≡ 0(mod9901),则不存在乘法逆元,p ≡ 1(mod9901),故(1+p+p2 +…+pe*b ) = (1+1+1+…+1) = e*b+1,否则用乘法逆元就可以解决。

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define N 50000005
#define mod 9901
#define IO ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
#define PII pair<int,int>
#define mp make_pair
using namespace std;
const int INF = 1000 * 1000 + 5;
typedef long long ll;
//freopen("D:\\in.txt","r",stdin);
//freopen("D:\\in.txt","w",stdout);
using namespace std;
int p[N],c[N];
int tot;

void divide(ll n)
{
    for(int i=2; i*i<=n; i++)
    {
        if(n%i == 0)
        {
            p[++tot] = i;
            c[tot] = 0;
            while(n%i==0)
            {
                n/=i;
                c[tot]++;
            }
        }
    }
    if(n>1)
    {
        p[++tot] = n;
        c[tot] = 1;
    }

}

ll qpow(ll a,ll b)
{
    ll ans=1;
    while(b)
    {
        if(b&1)
            ans = (ans*a)%mod;
        a = (a*a)%mod;
        b >>= 1;
    }
    return ans;
}

int main()
{
    ll a,b;
    scanf("%lld%lld",&a,&b);
    if(a==0)
    {
        printf("0\n");
        return 0;
    }
    divide(a);
    ll ans=1;
    for(int i=1; i<=tot; i++)
    {
        if((p[i]-1)%mod == 0)
        {
            ans = (b*c[i]+1)%mod * ans %mod;
            continue;
        }
        ll x = qpow(p[i],(ll)b*c[i]+1);//分子
        x = (x-1+mod) %mod;
        ll y = p[i]-1;//分母
        y = qpow(y,mod-2);//费马小定理求逆元
        ans = ans * x % mod * y % mod;
    }
    printf("%lld\n",ans);
    return 0;
}


3.一些例题

  • 阶乘分解
    数论——唯一分解定理_第2张图片
    思路:
    我们发现N!中的p的个数就是1~N中p的个数和,因此我们只要统计所有 p k i p_k^i pki出现的个数和即可求出答案。
    代码:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define N 1000005
#define mod 998244353
#define IO ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
#define PII pair<int,int>
#define mp make_pair
using namespace std;
const int INF = 1000 * 1000 + 5;
typedef long long ll;

//freopen("D:\\in.txt","r",stdin);
//freopen("D:\\in.txt","w",stdout);

using namespace std;

int primes[N],cnt;
bool vis[N];

void shai(int n)
{
    vis[1]=vis[0]=1;
    for(int i=2; i<=n; i++)
    {
        if(!vis[i]) primes[cnt++] = i;
        for(int j=0; primes[j]*i<=n && j<cnt; j++)
        {
            vis[primes[j]*i] = 1;
            if(i%primes[j]==0) break;
        }
    }
}

int main()
{
    IO;
    int n;
    cin>>n;
    shai(n);
    for(int i=0; i<cnt; i++)
    {
        int p = primes[i];
        int s = 0;
        for(int j=n; j; j/=p)   s += j/p;
        cout<<p<<" "<<s<<endl;
    }

    return 0;
}

变形1:求N!末尾0的个数。
思路:也就是求因子乘积为10的个数,即2,5因子数的最小值,而容易知道5的个数一定比2少,因此计算因子5的个数即可

int ans = 0;
while(n) 
{
    num += n / 5;
    n /= 5;
}
return ans;

类似的要求N!在二进制最低位1出现的位置,也就是求因子2出现的个数

变形2:求N!的因子的因子个数和
思路: N ! = p 1 e 1 ∗ p 2 e 2 ∗ . . . ∗ p r e r N!=p_1^{e_1}*p_2^{e_2}*...*p_r^{e_r} N!=p1e1p2e2...prer
p i e i p_i^{e_i} piei的选择有 e i + 1 种 e_i+1种 ei+1 p i 0 , p i 1 , . . . . . . , p i e 1 p_i^{0},p_i^1,......,p_i^{e_1} pi0,pi1,......,pie1
p i 0 p_i^{0} pi0有1个因子, p i 1 p_i^{1} pi1有2个因子, p i e 1 p_i^{e_1} pie1 e 1 + 1 e_1+1 e1+1个因子
故对于 p i p_i pi所贡献的因子数即为 ( e i + 1 ) ∗ ( e i + 2 ) 2 \frac{(e_i+1)*(e_i+2)} {2} 2(ei+1)(ei+2)
最后再将每一个p的因子数乘起来即是结果了
公式为 ∏ i = 1 r ( e i + 1 ) ∗ ( e i + 2 ) 2 \prod_{i=1}^r\frac{(e_i+1)*(e_i+2)} {2} i=1r2(ei+1)(ei+2)

题目链接:Divisors of the Divisors of An Integer(2018-2019 ACM-ICPC, Asia Dhaka Regional ContestC)

参考资料

《算法竞赛中的初等数论》(一)正文 0x00整除、0x10 整除相关(ACM / OI / MO)(十五万字符数论书)
夜深人静写算法(三)- 初等数论入门

你可能感兴趣的:(数学,算法,acm竞赛)