算术基本定理
【算术基本定理】
算术基本定理⼜称 唯⼀分解定理 :
• 任何⼀个⼤于1 的⾃然数 ,都可以唯⼀分解成有限个质数的乘积

【代码实现】
cpp
int c[N]; // c[i] 表⽰ i 这个质数出现的次数
void deprime(int x)
{
for(int i = 2; i <= x / i; i++)
{
int cnt = 0;
while(x % i == 0) // 只要有这个因⼦,就除尽,并且计数
{
x /= i;
cnt++;
}
c[i] += cnt;
}
if(x > 1) c[x]++; // 不要忘记判断最后⼀个质数
}
时间复杂度:
枚举到 n ,因此时间复杂度为 O ( N ) 。但是最优情况下会达到 O (log n ) 。
1 质因⼦分解
题⽬来源: 洛⾕
题⽬链接: P2043 质因⼦分解
难度系数: ★
题目描述
对 N! 进行质因子分解。
输入格式
输入数据仅有一行包含一个正整数 N,N≤10000。
输出格式
输出数据包含若干行,每行两个正整数 p,a,中间用一个空格隔开。表示 N! 包含 a 个质因子 p,要求按 p 的值从小到大输出。
输入输出样例
输入 #1复制
10
输出 #1复制
2 8
3 4
5 2
7 1
说明/提示
10!=3628800=(28)×(34)×(52)×7。
【解法】
对阶乘中的每⼀个数,分解质因数。
【参考代码】
cpp
#include <iostream>
using namespace std;
// 定义数组最大范围:题目要求 N ≤ 10000,所以开 10000+10 足够
const int N = 1e4 + 10;
// n:输入的阶乘上限;c数组:c[p] 记录质因数p在N!中出现的总次数
int n;
int c[N];
// 试除法分解单个数字x的质因数,并把次数累加到c数组
void deprime(int x)
{
// 枚举 2 到 √x 的所有数(试除法核心:因数成对出现,只需枚举到平方根)
for (int i = 2; i <= x / i; i++)
{
int cnt = 0; // 记录当前质因数i在x中出现的次数
// 只要x能被i整除,就反复除,统计次数
while (x % i == 0)
{
cnt++; // 次数+1
x /= i; // x除以i,缩小继续判断
}
c[i] += cnt; // 把当前质因数的次数累加到全局计数数组
}
// 特殊情况:如果最后x>1,说明剩下的x是一个质数(大于√原x的质因数)
if (x > 1)
c[x]++;
}
int main()
{
// 输入阶乘上限N
cin >> n;
// 遍历 2~n 的所有数,逐个分解质因数
for (int i = 2; i <= n; i++)
{
deprime(i);
}
// 遍历 2~n,输出有次数的质因数(按p从小到大)
for (int i = 2; i <= n; i++)
{
// 只有次数>0的质因数才输出
if (c[i])
{
cout << i << " " << c[i] << endl;
}
}
return 0;
}
2 阶乘分解
题⽬来源: 洛⾕
题⽬链接: P10495 阶乘分解
难度系数: ★★
题目描述
给定整数 N(3≤N≤106),试把阶乘 N! 分解质因数,按照算术基本定理的形式输出分解结果中的 pi 和 ci 即可。
输入格式
一个整数 N。
输出格式
N! 分解质因数后的结果,共若干行,每行一对 pi,ci,表示含有 pici 项。按照 pi 从小到大的顺序输出。
输入输出样例
输入 #1复制
5
输出 #1复制
2 3
3 1
5 1
【解法】
正难则反。
先筛出 [1, 1 e 6] 之间的质数,然后考虑包含某⼀个质数 j 次幂的数⼀共有多少个。
【参考代码】
cpp
#include <iostream>
using namespace std;
// 定义长整型别名,防止乘法溢出
typedef long long LL;
// 数组上限:题目要求 N ≤ 1e6,额外预留10个位置避免越界
const int N = 1e6 + 10;
// 全局变量:
int n; // 输入的阶乘上限 N
bool st[N]; // 标记数组:st[i] = true 表示 i 不是质数
int p[N], cnt; // p数组存储质数,cnt 记录质数的个数
// 线性筛(欧拉筛):快速筛出 1~n 范围内的所有质数
void get_prime()
{
// 遍历 2 到 n 的所有数
for (int i = 2; i <= n; i++)
{
// 如果 i 未被标记,说明是质数,存入 p 数组
if (!st[i])
{
p[++cnt] = i;
}
// 用已找到的质数筛除合数(线性筛核心)
for (int j = 1; 1LL * i * p[j] <= n; j++)
{
// 标记 i*p[j] 为合数
st[i * p[j]] = true;
// 关键:避免重复筛除,保证每个合数只被最小质因数筛一次
if (i % p[j] == 0)
{
break;
}
}
}
}
int main()
{
// 输入阶乘上限 N
cin >> n;
// 第一步:筛出 1~n 范围内的所有质数
get_prime();
// 第二步:遍历每个质数,计算其在 N! 中的次数
for (int i = 1; i <= cnt; i++)
{
int prime = p[i]; // 当前要计算的质数
int count = 0; // 记录该质数在 N! 中的总次数
// 公式:次数 = n//p + n//p² + n//p³ + ... 直到 p^k > n
for (LL j = prime; j <= n; j *= prime)
{
count += n / j;
}
// 输出质数和对应的次数
cout << prime << " " << count << endl;
}
return 0;
}