唯一分解

唯一分解定理


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

任何一个大于1的自然数N,如果不是质数,那么可以唯一分解成有限个质数的乘积。

  • 推论1:

任何一个大于1的自然数N可以表示成以下形式
p1 ^ e1 * p2 ^ e2 * …* pm ^ em
其中p1 < p2 < … < pm,且为质数
e1,e2……em 是正整数

  • 推论2:

任何一个大于1的自然数,要么所有质因子都小于等于sqrt(N),要么只有一个质因子大于sqrt(N),其余质因子都小于sqrt(N)。


唯一分解定理代码实现

要得到
p1 ^ e1 * p2 ^ e2 * …* pm ^ em
常见的分解方法有两个:

  • 先筛出素数,然后分解质因子

扫描2—sqrt(n)的每个质数prime[k],如果prime[k]能整除n,那么从n中除掉所有的因子prime[k],同时累计除掉prime[k]的个数
如果最终剩余的n大于1,那么说明2—sqrt(n)中没有一个质数是n的因子,即最后剩余的n是素数,是原来n的质因子

程序:

bool prime[maxn];
int pnum[maxn];
int cnt;
void is_prime(){        //素数筛
    prime[0] = prime[1] = 1;
    for(int i=2; i<maxn; i++){
        if(!prime[i]) pnum[cnt++] = i;
        for(int j=0; j<cnt && i*pnum[j]<maxn; j++){
            prime[i*pnum[j]] = 1;
            if(i%pnum[j]==0) break;
        }
    }
}
int p[30],e[30],tot;
void only(int n){       //唯一分解
    tot = 0;
    memset(e,0,sizeof(e));
    memset(p,0,sizeof(p));
    for(int i=0; i<cnt && pnum[i]<=sqrt(n); i++){   //遍历小于sqrt(n)的所有质因子
        if(n%pnum[i]) continue;     //如果不能整除n,跳过
        p[++tot] = pnum[i];         //可以整除,记录p
        while(n%pnum[i]==0){        //数出e的数量
            n/=pnum[i];
            e[tot]++;
        }
    }
    if(n>1){            //不为一:是原来n的质因子
        p[++tot] = n;
        e[tot] = 1;
    }
}

  • 直接质因子分解

扫描2—sqrt(n)的每个数d,如果d能整除n,则从n中除去所有的因子d,累计数量
因为一个合数的因子一定扫描到这个合数之前就被n除掉了,那么能整除的n一定是质数(能整除6,6之前已经被2,3除过了)

void only(int n){
    tot = 0;
    memset(e,0,sizeof(e));
    for(int i=2; i<=sqrt(n); i++){
        if(n%i) continue;
        p[++tot] = i;
        while(n%1==0){
            n/=i;
            e[tot]++;
        }
    }
    if(n>1){
        p[++tot] = n;
        e[tot] = 1;
    }
}

多次操作:先筛素数再分解,单次操作:直接分解。


  • 数据范围:确定开数组的大小

1——2e9中的数,任何数的 不同 质因子都不会超过10个(原因见下条),并且所有质因子的质数总和不会超过30

  • 最小的11个质数的乘积
    2 * 3 * 5 * 7 * 11 * 13 * 17 * 19 * 23 * 29 * 31 >2e9

  • 最小质数的31次方:
    2 ^ 31 > 2 e 9


阶乘分解:对n!进行唯一分解

n! = p1 ^ e1 * p2 ^ e2 * …* pm ^ em

  • 方法1:把1—n的每个数分别分解质因数,再把结果合并

    O(n * sqrt(n))

  • 方法2:先筛出1—n中的每个质数p,考虑N!中有多个质因子p

    (1)先筛出1–n中的每个质数p,考虑n!中有多少个质因子p

    (2)n!中质因子p的个数就等于1—n每个数包含质因子p的个数和

    (3)在1—n中,p的倍数,即至少包含一个质因子p的数有n/p个

    (4)p ^ 2 的倍数,即至少包含两个质因子p的数有n / (p ^ 2)个,总共2 * n / (p ^ 2)个质因子p 。 不过其中一个已经在前面计算过了,所以只加上n / (p ^ 2)

    (5)ans = sum of n / p ^ i (if p^I <= n). ——统计p的个数O(logn)

    (6)预处理质数p O(n)考虑p的数量:O(logn)总共——O(nlogn)

代码:

		素数筛————略
bool prime[maxn];
int pnum[maxn];
int cnt;
void is_prime(){   
}

		
int cal(int n,int p){       //统计在n!中p的个数
    int ans = 0;
    while(n){
        ans += n/p;
        n/=p;
    }
    return ans;
}

int p[maxn],e[maxn],tot;  

void fac_only(int n){       //分解n!

    for(int i=0; i<cnt && pnum[i]<=n; i++){
        p[++tot] = pnum[i]; 
        e[tot] = cal(n,pnum[i]);
    }
    
}

求n的正约数集合——暴力

约数,又称因数。整数a除以整数b(b≠0) 除得的商正好是整数而没有余数,我们就说a能被b整除,或b能整除a。a称为b的倍数,b称为a的约数

若d大于等于sqrt(n)是n的约数,那么n/d 小于等于sqrt(n)也是n的约数,约数总是成对出现的 (除了完全平方数,单独出现)

1e9之内的自然数中,约数最多的自然数约有1536个

int fac[2100],cnt;
void get(int n){
    for(int i=1; i<=sqrt(n); i++){
        if(n%i) 
        	continue; //不是n的因子 跳过
        fac[++cnt] = i;
        if(i*i!=n) fac[++cnt] = n/i; //成对出现
    }
}

一个数的正约数个数,正约数和

方法一:上面暴力除法,在过程中统计

方法二:借助唯一分解
p1 ^ e1 * p2 ^ e2 * …* pm ^ em

约数个数:
(e1 + 1) * (e2 + 1) * …… * (em+1)**

原因:
n分解成e1个p1相乘,乘e2个p2相乘……n就是由这些数乘起来的。
随便抽出来k个数相乘,一定是n的约数,抽法总共:(e1 + 1) * (e2 + 1) * …… * (em+1)。
每种方法求出的结果都是n的约数,所以约数个数是(e1 + 1) * (e2 + 1) * …… * (em+1)。

约数和:
(1+p1 + p1 ^2 + p1 ^3 + …+p1 ^ e1) * ( 1+ p2 + p2 ^ 2 + …+ p2 ^ e2 ) * …* (1 + pm + pm ^ 1 + … + pm ^ em)

注意:
(1+p1 + p1 ^2 + p1 ^3 + …+p1 ^ e1)等比数列求和公式
(1 - p1 ^ (e1 + 1)) / (1 - p1)
最后再连乘

	 唯一分解的代码————略
	
	(1)约数个数
	
int count(int n){
    only(n);    //唯一分解
    int ans = 1;
    for(int i=1; i<=tot; i++) //tot 是p和e的个数
        ans *= e[i]+1;
    return ans;
}2)约数和

int sum(int n){
    only(n);    //唯一分解
    int ans = 1;
    for(int i=1; i<=tot; i++) //tot 是p和e的个数
        ans *= (1-pow(p[i],e[i]+1))/(1-p[i]);
    return ans;
}

n个数相乘求其正约数个数

注意1:
不能一个个分解然后相加——有重复
每次都执行only操作
最后一起计算
注意2:
注意n==0的情况
注意3: 只求个数,可以不用写p————灵活处理问题

#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<iomanip>
#include<vector>
#include<cstring>
#include<string>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int mod = 1e9+7;
const int maxn = 1e6+5;
ll e[maxn];
void only(int n){
    for(int i=2; i<=sqrt(n); i++){  //注意从2开始
        if(n%i) continue;
        while(n%i==0){
            n /= i;
            e[i]++;
        }
    }
    if(n>1) e[n]++;
}
int main(){
    int T,n;
    scanf("%d",&T); //输入T个数
    bool flag = 0;
    
    while(T--){
        scanf("%d",&n);
        if(n==0) flag = 1;
        only(n);
    }
    ll ans = 1;
    if(flag) ans = 0;
    for(int i=1; i<maxn; i++){
        ans=(ans*(e[i]+1))%mod;
    }
    printf("%lld\n",ans);
    return 0;
}

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值