1. 题目来源
链接:310. 阶乘分解
2. 题目说明
3. 题目解析
方法一:暴力+常规解法
即便冒着会 TLE
的风险,也要暴力试上一发,毕竟暴力出奇迹。设计到唯一分解定理即算术基本定理,数论的基础知识,在这就不赘述了。暴力思路如下:
- 首先考虑
n=1
中的情况,按照质因子分解规则来讲,1 无法进行分解,所以不考虑这个特殊情况,代码能够AC
,加上了这句话好像也没啥问题,顺利AC
,可能数据不全面吧 - 首先我们开辟一个
cnt
数组用以记录每个数的质因子个数 - 第一层
for
循环:从 2 到n
每个数顺序遍历,记为num
- 第二层
for
循环:对上层循环num
求出它的质因子分解结果,并在cnt
数组中进行存储。在此用到了一个对偶逻辑的技巧,即if (num % j) continue;
原有想表达的逻辑是,若j
是num
的因子代码为num % j == 0
则会加上一个大括号,并在大括号内部进行继续写剩余的代码,那么就会多一层大括号。所以采用对偶逻辑对其进行简化 - 然后对
num
进行质因子分解,通过一个while
循环,将num
中的所有的j
因子全部剔除并记录 - 第二层循环结束后若
num
不为 1,那么这个剩余的num
一定也是质数,所以在cnt
数组中进行记录 - 最后按顺序输出
cnt
数组中结果就行了
这个虽然是两个 for
循环的暴力算法,但是还是隐藏的有优化点的,要是真的纯的两个 for
循环可能真就 TLE
了。在此可以思考一下,为什么我们直接会认定第二层 for
循环内部的 while
条件中的 j
一定为素数呢?在此考虑反证法,假设 j
是一个合数,那么一定可以分解为若干个素数相乘的形式,所以其中一定有一个较小的素数,我们的 j
是从小到大进行枚举的,那么在之前已经将所有的小于 j
的素数进行记录并剔除掉了,j
作为合数的话是无法完成质因子分解的,所以 j
现在不可能是一个合数,j
一定是一个素数。
质因子分解的时间复杂度是要远远小于
O
(
n
)
O(\sqrt{n})
O(n),只有当 num
是一个质数的时候,它的时间复杂度才会等于
O
(
n
)
O(\sqrt{n})
O(n) ,若 num
不为素数时,num
值会迅速减小,即程序的上界会不断减小,所以远远小于
O
(
n
)
O(\sqrt{n})
O(n)。
参见代码如下:
#include <iostream>
using namespace std;
const int MAXN = 1e6 + 50;
int cnt[MAXN];
int main() {
int n;
cin >> n;
for (int i = 2; i <= n; ++i) {
int num = i;
for (int j = 2; j * j <= num; ++j) {
if (num % j) continue;
while (num % j == 0) cnt[j] += 1, num /= j;
}
cnt[num] += 1;
}
for (int i = 2; i <= n; ++i) {
if (!cnt[i]) continue;
cout << i << " " << cnt[i] << endl;
}
return 0;
}
方法二:线性筛+因子分解技巧+巧妙解法
举个简单的例子,如对 8 的阶乘进行质因子分解,那么可写为 1、2、3、4、5、6、7、8
顺序对这 8 个数进行质因子分解,那么首先来看看,若以 2 为底数,则指数为 4 的话,应该就是 8 / 2 = 4
,即会产生 4 个 2,那么产生三个 2 的话就是 6 / 4 = 3
,同理 4 会产生两个 2,2 就保持自己产生一个 2,那么就能够总结一个规律,关于质因子 2 的话,若想统计它的指数,则公式为:
n
2
+
n
4
+
n
8
+
⋯
+
n
2
⌊
l
o
g
2
n
⌋
\frac{n}{ 2}+\frac{n}{ 4}+\frac{n}{ 8}+\cdots+\frac{n}{ 2^{\lfloor log_{2}^n\rfloor}}
2n+4n+8n+⋯+2⌊log2n⌋n就计算这个式子的累加和就能得到 2 的指数大小。同理计算 3 的话,就将 2 变成 3,4 变成 9,就行了。
那么基于上述思想,我们有以下思路:
- 首先,需要一张素数表,在此采用线性筛的方法进行初始化
- 遍历素数表,按照上述因子分解技巧,求出每个素数的质因子分解个数
- 顺从大到小顺序输出各个质因子及对应个数即可
时间复杂度 O ( n ) O(n) O(n),效率相当高。在这个题目中需要学习到因子分解的技巧,这个也是相当关键的一步。还有就是这个线性筛的板子,没啥好说的,参考上篇博文即可。
参见代码如下:
#include <iostream>
using namespace std;
const int MAXN = 1e6 + 50;
int cnt[MAXN], prime[MAXN];
void init_prime() {
for (int i = 2; i <= MAXN; ++i) {
if (!prime[i]) {
prime[++prime[0]] = i;
}
for (int j = 1; j <= prime[0]; ++j) {
if (i * prime[j] > MAXN) break;
prime[i * prime[j]] = 1;
if (i % prime[j] == 0) break;
}
}
return;
}
int main() {
int n;
cin >> n;
init_prime();
for (int i = 1; i <= prime[0] and prime[i] <= n; ++i) {
int cnt = 0, num = n;
while (num) {
cnt += num / prime[i];
num /= prime[i];
}
cout << prime[i] << " " << cnt << endl;
}
return 0;
}