唯一分解定理
唯一分解定理(算数基本定理):
任何一个大于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;
}