[数论基础] 2. 阶乘分解(暴力、因子分解、线性筛、巧妙解法)

1. 题目来源

链接:310. 阶乘分解

2. 题目说明

在这里插入图片描述

3. 题目解析

方法一:暴力+常规解法

即便冒着会 TLE 的风险,也要暴力试上一发,毕竟暴力出奇迹。设计到唯一分解定理即算术基本定理,数论的基础知识,在这就不赘述了。暴力思路如下:

  • 首先考虑 n=1 中的情况,按照质因子分解规则来讲,1 无法进行分解,所以不考虑这个特殊情况,代码能够 AC,加上了这句话好像也没啥问题,顺利 AC,可能数据不全面吧
  • 首先我们开辟一个 cnt 数组用以记录每个数的质因子个数
  • 第一层 for 循环:从 2 到 n 每个数顺序遍历,记为 num
  • 第二层 for 循环:对上层循环 num 求出它的质因子分解结果,并在 cnt 数组中进行存储。在此用到了一个对偶逻辑的技巧,即 if (num % j) continue; 原有想表达的逻辑是,若 jnum 的因子代码为 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++2log2nn就计算这个式子的累加和就能得到 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;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ypuyu

如果帮助到你,可以请作者喝水~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值