一、单个数质因数分解
直接上代码:
#include <bits/stdc++.h>
using namespace std;
/**
* 功能:分解质数因数
* @param a 待分解的数字
*/
const int N = 1010;
int primes[N]; //质数因子数组
int idx; //质数因子数组下标游标
int mc[N]; //mc对应着幂次
void Decomposition(int a) {
//清空,防止重复
memset(primes, 0, sizeof primes);
memset(mc, 0, sizeof mc);
idx = 0;
//开始
for (int i = 2; i * i <= a; i++) {
//如果发现a的一个因子i
if (a % i == 0) {
primes[++idx] = i; //质因子数组维护
mc[idx] = 1; //指数幂次数+1
a /= i; //去掉这个因子
while (a % i == 0) { //除干净它
mc[idx]++; //指数幂次数+1
a /= i; //继续缩小
}
}
}
//可能剩余一个很大的质因子数
if (a > 1) {
primes[++idx] = a;
mc[idx] = 1;
}
}
int main() {
//求2~100区间内所有数字的质因子
for (int i = 2; i <= 100; i++) {
cout << i << ": ";
//分解质因子
Decomposition(i);
//遍历输出质因子
for (int j = 1; j <= idx; j++) { //遍历每个质因子
for (int k = 1; k <= mc[j]; k++) { //遍历质因子的个数
cout << primes[j];
//最后一个因子,并且是最后一个因子的最后一个输出,后面不加*
if (j == idx && k == mc[j]) continue;
cout << "*";
}
}
cout << endl;
}
return 0;
}
适用场景:单个数字的质因数分解。如果是区间内质因数分解,这个办法就太慢了。
二、区间内质因数分解
1、朴素版本
思路:
1、先 get_primes(sqrt(r))
取得所有\(r\)的所有可能小质数因子(采用欧拉筛,即线筛)。
2、双重循环:外层循环遍历\([l,r]\)之间的每个数字,内层循环遍历筛出来的每一个质数因子,如果能被整除掉,就一直除,直到不再包含这个质数因子。
3、除完已知小质数列表后,\(i\)有两种可能 ,第一种\(i=1\),表示被所有小质数因数除干净了;另一种是\(i>1\),表示还存在一个大于\(sqrt(x)\)的大质数因数。将找到的所有可能质数因子记录到一个vector<int> v[N]
中,
优点:
因为采用了先穷举根号内质数因数的办法,所以去掉了很多无用的尝试,对比传统暴力的分解质因数方法
#include <bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
typedef long long LL;
LL l = 1, r = 2000000;
const int N = 1e7 + 10;
//欧拉筛[线性筛法]
int primes[N], cnt; // primes[]存储所有素数
bool st[N]; // st[x]存储x是否被筛掉
void get_primes(int n) {
for (int i = 2; i <= n; i++) {
if (!st[i]) primes[cnt++] = i;
for (int j = 0; primes[j] <= n / i; j++) {
st[primes[j] * i] = true;
if (i % primes[j] == 0) break;
}
}
}
vector<int> v[N];
int main() {
/**=====================================================***/
//计时开始
clock_t startTime= clock();
/**=====================================================***/
//1、筛出小于sqrt(r)的所有质数
get_primes(sqrt(r));
//2、遍历每个数字,求质因子
for (LL i = l; i <= r; i++) {
int t = i;
for (int j = 0; j < cnt && primes[j] * primes[j] <= i; j++) {
if (i % primes[j] == 0) {
v[i].push_back(primes[j]);
//除干净这个质数因子
while (t % primes[j] == 0) t /= primes[j];
}
}
if (t > 1) v[i].push_back(t);
}
//输出质数因子有哪些
for (int i = l; i <= r; i++) {
cout << i << ": ";
for (auto c:v[i]) cout << c << " ";
cout << endl;
}
cout << endl;
/**************************************************************/
//计时结束
clock_t endTime = clock();
cout << "The run time is: " <<(double)(endTime - startTime) / CLOCKS_PER_SEC << "s" << endl;
/**=====================================================***/
//The run time is: 4.273s
return 0;
}
2、优化版本
前导知识:整数除法向上取整
在前面基础上进行优化。
原理:上面是双重循环,遍历每一个数字求质数因子。 这里是遍历所有质数因子,成倍的筛出质数的倍数,是单层循环,速度当然更快,类似于埃筛的思路。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
LL l = 1, r = 2000000;
const int N = 1e7 + 10;
//欧拉筛[线性筛法]
int primes[N], cnt; // primes[]存储所有素数
bool st[N]; // st[x]存储x是否被筛掉
void get_primes(int n) {
for (int i = 2; i <= n; i++) {
if (!st[i]) primes[cnt++] = i;
for (int j = 0; primes[j] <= n / i; j++) {
st[primes[j] * i] = true;
if (i % primes[j] == 0) break;
}
}
}
vector<int> v[N];
int main() {
/**=====================================================***/
//计时开始
clock_t startTime = clock();
/**=====================================================***/
//1、筛出小于sqrt(r)的所有质数
get_primes(sqrt(r));
//遍历每个小质数因子
for (int i = 0; i < cnt; i++) {
int p = primes[i];
//1、利用数组下标的位移,记录数据
//2、找到大于l的第一个p的倍数,然后,每次增加p,相当于找出p的整数倍
for (LL j = ((l - 1) / p + 1) * p; j <= r; j += p)
//在每个倍数中记录下这个共同的质数因子p,比如 6里面包含2,8里面包含2,10里面包含2,相当于埃筛一次找到所有p的整数倍
v[j - l].push_back(p); //标识数组vec[j-l],也就是真实的l这个数字中,存在p这个因子。
}
//如果还存在大的质数因子
for (LL i = l; i <= r; i++) {
LL tmp = i; //将i拷贝出来给了tmp,tmp要不断的减少啦,而i要保留。
//遍历每个已经找到的因子
for (int p: v[i - l]) {
//除干净
while ((tmp % p) == 0) tmp /= p;
}
//如果还存在大的质数因子
if (tmp > 1)v[i - l].push_back(tmp);
}
//输出质数因子有哪些
for (int i = l; i <= r; i++) {
cout << i << ": ";
for (auto c:v[i - l]) cout << c << " ";
cout << endl;
}
cout << endl;
/**************************************************************/
//计时结束
clock_t endTime = clock();
cout << "The run time is: " << (double) (endTime - startTime) / CLOCKS_PER_SEC << "s" << endl;
/**=====================================================***/
//第一种方法用时:
//The run time is: 4.273s
//第二种方法用时:
//The run time is: 1.677s
return 0;
}
实测性能提高一倍以上。