前言
先讲唯一分解定理
是个啥,然后用一个经典例题来巩固一下就O~er了。
一、理论
-
定义:
任意一个正整数n
(n
>1),一定存在一个全为质数的集合(这个集合中所有元素 x i ≤ n xi \leq n xi≤n) {P1e1,P2e2,P3e3……,Pkek} (Pi>1,ei>=1,i>=1),这个集合所有元素相乘,结果就等于n
。 -
示例1——30的唯一分解:
因为:30=2x3x5.
所以30的唯一分解后得到一个质数集合:{2,3,5}
. -
示例2——84的唯一分解:
因为:84= 2 2 2^{\color{red}2} 22x3x7
所以84通过唯一分解得到的质数集合:{2,3,7}
. -
补充:
以上的质数集合必定是唯一的!
如果你发现了不止一个满足上述条件的集合,您过来打我,我发地址给您。🫡🫡🫡
二、经典例题
1、题目描述
2、算法原理&AC代码
什么是质因子?
这个
因子
是质数
,那么就是质因子
。
那么什么是因子
呢?很简单,如果一个整数x
,可以整除原来的数n
(n
/x
==整数
),那么x
就是n
的因子。
题目要求给定一个数N ,(
1
≤
N
≤
1
0
12
1 \leq N \leq 10^{12}
1≤N≤1012),求对应的所有质因数。这不就是求一个数,通过唯一分解定理得到的所有质数的集合吗?也就是说,N=30,那么答案就是{2,3,5
};N=84 ,那么答案就是{2,3,7
}。
所以思路就清晰了,我们直接在区间[2,N]遍历所有的质数,判断这个质数是否是唯一分解定理里面的集合,是就打印,不是就往右边遍历,直到为N。
public class Main {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
int N=scan.nextInt();
for(int i=2;i<N+1;i++){
}
}
}
那么如何i
是否属于N的质数集合呢?
接下来的文字都很重要,必须认真看了哈。
请看这个式子:
i1e1
x
i2e2x
i3e3x
……x
ikek=
N
( e ≥ 1 e\geq 1 e≥1)
我们需要的答案实际上就是等式左边所有指数的底数: i1、i2、i3、……ik .
这些底数i,有一个共同的特点,就是它们都是整数,所以N
除以任意一个i
,其结果也是一个整数。
所以判断当前枚举的i是否是答案,直接N%i==0即可,等于零是整数,说明这个i就是质因子。我们这种操作实际上就有点想拿着答案去反推过程:
public class Main {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
long N=scan.nextLong();
for(Long i=2;i<N+1;i++){
//获取可能的质数
if(N%i==0){
System.out.println(i+" ");
}
}
}
}
代码这样些就完成了吗?
其实没有,每当我们遍历到一个需要打印的i后,需要更新N
的大小:
public class Main {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
long N=scan.nextLong();
for(long i=2;i<N+1;i++){
//获取可能的质数
if(N%i==0){
System.out.println(i+" ");
}
//更新N,保证后续遍历的正确性
while(N%i==0){
N/=i;
}
}
}
}
这样做的目的是确保我们之前的式子是一致成立的:
i1e1 x
i2e2 x
i3e3 x
…… x
ikek =
N
假如 i1 已经遍历到并且打印了,并且e1=2,那么通过while循环式子就会变成:
i2e2 x
i3e3 x
…… x
ikek =
N / i1e1
这充分利用了唯一分解定理,来准确获取质因子。
最后如果问题规模不大,那么问题也就解决了,但是测试用例中出现了时间超限:
所以我们需要对上面这个代码做一下优化:
原来代码的时间复杂度实际上我们只需要改造for循环
,我们可以把循环数量降低一点(最终的AC代码
):
import java.util.Scanner;
// 1:无需package
// 2: 类名必须Main, 不可修改
public class Main {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
long N=scan.nextLong();
//变成平方根
for(long i=2;i<=Math.sqrt(N);i++){
if(N%i==0)System.out.print(i+" ");
while(N%i==0){
N/=i;
}
}
//判断是否会遗漏大于N的平方根的质因子,遗漏补上
if(N>1)System.out.print(N);
}
}
可以这样操作的原因是因数永远是成对出现的:(如果您找到不成对出现的因子,您来打我,我把地址发给您。🫡🫡🫡)
-
36的因数:
{2,18} {4,9} {6,6} -
27的因数:
{3,9} -
9的因数:
{3,3} -
……
还记得刚才的代码吗?
if(N%i==0)System.out.print(i+" ");
while(N%i==0){
N/=i;
}
我们在从左到右枚举每一个可能的数字,当枚举到一个可能的质数,这个质数一定对应着另一个因子。比如对于36,枚举到二的时候,那么一定对应另一个因子18(因为2x18=36),当我们打印这个因子后,我们会进入while
循环,每取一次模,对应的因子就会-1,直到右侧因子每%完。因此我们只需要从2枚举到
36
=
6
\sqrt{36} = 6
36=6即可。
这些成对的因子有这种特性:要么一个小于
N
\sqrt{N}
N,一个大于
N
\sqrt{N}
N;要么两个因子都等于
N
\sqrt{N}
N;但绝不可能一对因子,全部大于
N
\sqrt{N}
N。
所以i,只用枚举到
N
\sqrt{N}
N即可,为了避免遗漏掉大于
N
\sqrt{N}
N的因子(i没有遍历到),我们再最后增添一个if条件句即可。
这种题目说实话,对与咱们没有数学天分的人来说,我觉得还是很有难度的。因为它几乎考的就是数学思维。因此如果一遍没看懂,不用气馁,回头可以再仔细看一下。欢迎留言提问~~