【勒让德公式】欧拉筛-阶乘分解
阶乘分解题解
题目传送门
197. 阶乘分解
一、题目描述
给定整数N,将阶乘N!分解质因数,按照算术基本定理的形式输出分解结果中的质因数pᵢ和对应的指数cᵢ。
二、题目分析
- 需要将N!表示为质数的幂次乘积形式
- 关键在于高效计算每个质数在N!中的出现次数
- 需要先找出所有≤N的质数
三、解题思路
- 使用欧拉筛法预处理所有≤N的质数
- 对于每个质数p,计算在N!中的次数:
- 计算⌊N/p⌋ + ⌊N/p²⌋ + ⌊N/p³⌋ + … 直到pᵏ > N
- 输出所有质数及其对应的次数
四、算法讲解
欧拉筛法(线性筛)
- 原理:每个合数只被它的最小质因数筛掉
- 时间复杂度:O(n)
- 实现步骤:
- 遍历2到N的每个数i
- 如果i未被标记,则是质数,加入质数表
- 用当前数i与已知质数相乘标记合数
- 当i能被质数整除时停止标记
阶乘质因数分解
- 勒让德公式:计算质数p在N!中的次数为:
∑⌊N/pᵏ⌋ (k=1,2,…直到pᵏ>N) - 例子:计算5!中2的指数
⌊5/2⌋=2, ⌊5/4⌋=1 → 2+1=3
五、代码实现
#include <bits/stdc++.h>
using namespace std;
// #define int long long
const int N = 1e6 + 10;
int prime[N], cnt; // 质数表和计数器
int st[N]; // 标记是否为合数
int n;
// 欧拉筛法预处理质数
void init(int n)
{
for (int i = 2; i <= n; i ++)
{
if(!st[i]) // i是质数
prime[cnt ++] = i; // 加入质数表
// 用当前数和质数表中的数标记合数
for (int j = 0; prime[j] * i <= n; j ++)
{
st[i * prime[j]] = 1; // 标记合数
if(i % prime[j] == 0) break; // 保证每个合数只被最小质因数筛掉
}
}
}
void solve()
{
cin >> n;
init(n); // 预处理≤n的所有质数
// 对每个质数计算在n!中的次数
for (int i = 0; i < cnt; i ++)
{
int p = prime[i]; // 当前质数
int s = 0; // 计数器
// 计算p在n!中的次数:n/p + n/p² + n/p³ + ...
for (int j = n; j ; j /= p)
{
s += j / p;
}
cout << p << " " << s << "\n"; // 输出质数和次数
}
}
signed main()
{
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
solve();
return 0;
}
六、重点细节
- 筛法范围:只需要筛到N,因为N!的质因数不会超过N
- 次数计算:使用勒让德公式,通过不断除以p来计算总次数
- 效率优化:欧拉筛法保证质数预处理高效,次数计算也是对数级别
- 边界处理:N≥3,无需处理特殊情况
七、复杂度分析
- 时间复杂度:
- 预处理质数:O(n)
- 计算每个质数的次数:O(cnt * logₚN) ≈ O(n/lnn * lnn) = O(n)
- 空间复杂度:O(n),用于存储质数表和标记数组
八、总结
本题通过欧拉筛法高效预处理质数,再应用勒让德公式计算每个质数在阶乘中的次数,实现了对阶乘的质因数分解。算法结合了数论中的筛法和阶乘性质,在O(n)时间复杂度内解决问题,是质因数分解的经典应用。