任何一个大于1的整数n都可以分解成若干个质数的连乘积,如果不计各个质数的顺序,那么这种分解是惟一的,即若n>1,则有
n = ∏ p i j n=\prod{p^j_i} n=∏pij
这里的 p i p_i pi是质数。可以进行简单证明,假设 p i p_i pi是合数,那么它可以接着分解为两个数相乘的形式,所以最后 p i p_i pi一定是质数。
模板代码其实也是唯一分解定理的直接应用,给一个整数n,问有多少个质数是n的约数。这里就需要进行分解,也就是用到了唯一分解定理,我们直接上代码,然后逐一解释难懂的地方。
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
long n = scanner.nextLong();
int ans = 0;
for (int i = 2; i <= Math.sqrt(n); i++) {
if(n%i==0) ans++;
while(n%i == 0) {
n = n / i;
}
}
if(n > 1) {
ans++;
}
System.out.println(ans);
}
}
一般n可能给的很大,注意最好用long类型
我们如果要求一个数的因数,会从1开始遍历进行试除,那么应该遍历到哪里呢?是n吗?其实遍历到 n \sqrt{n} n就可以了。因为如果找到了一个因子为a,那么大于 n \sqrt{n} n的另一个因子就是n/a
很明显这个for循环我们是在采用试除法来求n的因子,但是我们如何保证求到的因子是质因子呢?这就是里面的while循环的作用了。给大家举个例子,n=36,6是它的因子,但是不是质因子,那么我们会不会遍历到它呢?i=2时,在while循环里就把36里的所有2都除没了,此时n=9。i=3时,在while循环里就把36里的所有3都除没了,此时n=1。那么此时的n里面已经不包含6了,因为6是由2和3构成的,在遍历到6之前,n里的所有的2和3都没有了,自然也就没有6了。这就是while循环的作用,他保证了我们找到的因子一定是质数。
抽象一点证明,假设q是合数且是n的因子,因为q是合数所有可以被表示成 q = p 1 ∗ p 2 q=p_1*p_2 q=p1∗p2,而 p 1 p_1 p1和 p 2 p_2 p2一定比q小,那么他们一定会在while里面被除去,因此遍历到q时不再包含它们,自然也不会有q。
最后一个地方,为什么在代码的最后要加一个if判断呢?还是给大家举一个例子,比如n=396,i=2时,n=99。n=3时,n=11。当i=4时i> n \sqrt{n} n,所以for循环退出了。但是你也可以发现,11也是n的一个质因子,所以我们在最后要判断一下,防止把这种情况漏了。
练习题目
(1)求正整数n的正因子个数
假设n的分解公式如下,
n = p 0 j 0 ∗ p 1 j 1 ∗ p 2 j 2 . . . ∗ p k j k n=p^{j_0}_0*p^{j_1}_1*p^{j_2}_2...*p^{j_k}_k n=p0j0∗p1j1∗p2j2...∗pkjk
则n的因子个数为 ( j 0 + 1 ) ∗ ( j 1 + 1 ) ∗ ( j 2 + 1 ) . . . ∗ ( j k + 1 ) (j_0+1)*(j_1+1)*(j_2+1)...*(j_k+1) (j0+1)∗(j1+1)∗(j2+1)...∗(jk+1)个。
简单理解一下,对于 2 3 ∗ 3 2 ∗ 5 3 2^{3}*3^{2}*5^{3} 23∗32∗53来说,可以选择一个2,其余质因子不选,则2是其因子,也可以选择两个2和一个3,则12是其因子,总的来说,假设n包含m个质因子p,则对于p我有m+1中选择,即[0,m]。
做个类比,我有红球3个,绿球5个,他们共有多少种不同的组合,肯定是46吧,那么n的因子个数为 ( j 0 + 1 ) ∗ ( j 1 + 1 ) ∗ ( j 2 + 1 ) . . . ∗ ( j k + 1 ) (j_0+1)*(j_1+1)*(j_2+1)...*(j_k+1) (j0+1)∗(j1+1)∗(j2+1)...∗(jk+1)同理。
模板代码如下,
import java.util.Scanner;
public class 约数个数 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
long n = 1200000;
int ans = 1;
for (int i = 2; i <= Math.sqrt(n); i++) {
int cnt = 0;
while(n%i == 0) {
n = n / i;
cnt++;
}
ans *=(cnt+1);
}
if(n > 1) {
ans *= 2;
}
System.out.println(ans);
}
}
练习题目
(2)求正整数n的所有正因子之和
假设n的分解公式如下,
n = p 0 j 0 ∗ p 1 j 1 ∗ p 2 j 2 . . . ∗ p k j k n=p^{j_0}_0*p^{j_1}_1*p^{j_2}_2...*p^{j_k}_k n=p0j0∗p1j1∗p2j2...∗pkjk
则n的所有正因子之和为 s u m = ( p 0 0 + p 0 1 + . . . + p 0 j 0 ) ∗ ( p 1 0 + p 1 1 + . . . + p 1 j 1 ) ∗ . . . ∗ ( p k 0 + p k 1 + . . . + p k j k ) sum=(p_0^0+p_0^1+...+p_0^{j_0})*(p_1^0+p_1^1+...+p_1^{j_1})*...*(p_k^0+p_k^1+...+p_k^{j_k}) sum=(p00+p01+...+p0j0)∗(p10+p11+...+p1j1)∗...∗(pk0+pk1+...+pkjk)个。
这里如果正向推导的话不是很简单,其实对于 p 0 0 p_0^0 p00而言,能够和它相乘成为n的因子的数为 p 1 p_1 p1的幂次里选一个幂次, p 2 p_2 p2的幂次里选一个幂次… p k p_k pk的幂次里选一个幂次,当然对于 p i p_i pi的幂次都会被选择,只是不是同时被选择,既然不是同时,就把他们写成加法,式子 p 0 0 ∗ ( p 1 0 + p 1 1 + . . . + p 1 j 1 ) p_0^0*(p_1^0+p_1^1+...+p_1^{j_1}) p00∗(p10+p11+...+p1j1)表示的是从 p 1 p_1 p1的所有幂次里进行选择与 p 0 0 p_0^0 p00组成一个因子,扩展到其它质数就是 p 0 0 ∗ ( p 1 0 + p 1 1 + . . . + p 1 j 1 ) ∗ . . . ∗ ( p k 0 + p k 1 + . . . + p k j k ) p_0^0*(p_1^0+p_1^1+...+p_1^{j_1})*...*(p_k^0+p_k^1+...+p_k^{j_k}) p00∗(p10+p11+...+p1j1)∗...∗(pk0+pk1+...+pkjk),然后再找对于 p 0 1 p_0^1 p01能够和它相乘成为n的因子的数,可以表示为 p 0 1 ∗ ( p 1 0 + p 1 1 + . . . + p 1 j 1 ) ∗ . . . ∗ ( p k 0 + p k 1 + . . . + p k j k ) p_0^1*(p_1^0+p_1^1+...+p_1^{j_1})*...*(p_k^0+p_k^1+...+p_k^{j_k}) p01∗(p10+p11+...+p1j1)∗...∗(pk0+pk1+...+pkjk),把 p 0 p_0 p0的所有幂次都考虑到就是 ( p 0 0 + p 0 1 + . . . + p 0 j 0 ) ∗ ( p 1 0 + p 1 1 + . . . + p 1 j 1 ) ∗ . . . ∗ ( p k 0 + p k 1 + . . . + p k j k ) (p_0^0+p_0^1+...+p_0^{j_0})*(p_1^0+p_1^1+...+p_1^{j_1})*...*(p_k^0+p_k^1+...+p_k^{j_k}) (p00+p01+...+p0j0)∗(p10+p11+...+p1j1)∗...∗(pk0+pk1+...+pkjk)
问题描述
定义阶乘 n! = 1 × 2 × 3 × ··· × n。
请问 100! (100 的阶乘)有多少个约数。
答案提交
这是一道结果填空的题,你只需要算出结果后提交即可。
本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。
这是蓝桥杯国赛的一道原题。求一个数的约数个数可以用唯一分解定理在 O ( n ) O(\sqrt{n}) O(n)的时间复杂度内求解。但是100的阶乘确实有点大了,你要是把100的阶乘求出来再去求会超时,并且这个数的存储也是一个问题,当然用java的大整数是可以存的。
这里只能舍弃掉求100的阶乘再求它的约数个数的思路,那应该怎么求呢?回顾一下利用唯一分解定理求解约数个数的过程,先对数字n进行质因数分解,得到式子 n = p 0 j 0 ∗ p 1 j 1 ∗ p 2 j 2 . . . ∗ p k j k n=p^{j_0}_0*p^{j_1}_1*p^{j_2}_2...*p^{j_k}_k n=p0j0∗p1j1∗p2j2...∗pkjk,然后可以求得n的因子个数为 ( j 0 + 1 ) ∗ ( j 1 + 1 ) ∗ ( j 2 + 1 ) . . . ∗ ( j k + 1 ) (j_0+1)*(j_1+1)*(j_2+1)...*(j_k+1) (j0+1)∗(j1+1)∗(j2+1)...∗(jk+1)。其实我们只需要知道数字n里面包含几个 p i p_i pi即可。对于 100 ! = 1 ∗ 2 ∗ 3 ∗ 4... ∗ 100 100!=1*2*3*4...*100 100!=1∗2∗3∗4...∗100,我们可以计算出2里面包含数字2的个数为 a 1 a_1 a1,3里面包含数字2的个数为 a 2 a_2 a2,4里面包含数字2的个数为 a 3 a_3 a3,5里面包含数字2的个数为 a 4 a_4 a4,以此类推直到求到100,那么100!里面包含数字2的个数就是 a 1 + a 2 + a 3 . . . a 99 a_1+a_2+a_3...a_{99} a1+a2+a3...a99。综上我们可以依次对1到100里面的每一个数进行质因数分解,得到的值累加就可以了,最终就可以求出来100进行质因子分解的结果。然后再按照求因子个数的方法进行求解就可以了。
public class Main {
public static void main(String[] args) {
int p[] = new int[105];
for(int i = 2;i <= 100;i++) {
int n = i;
for(int j = 2;j * j <= n;j++) {
while(n%j==0) {
p[j]++;
n/=j;
}
}
if(n > 0) p[n]++;
}
long ans = 1;
for(int i = 2;i <= 100;i++) {
ans *= (p[i]+1);
}
System.out.println(ans);
}
}
问题描述
学习了约数后,小明对于约数很好奇,他发现,给定一个正整数 t,总是可
以找到含有 t 个约数的整数。小明对于含有 t 个约数的最小数非常感兴趣,并
把它定义为 S t S_t St 。
例如 S 1 S_1 S1 = 1, S 2 S_2 S2 = 2, S 3 S_3 S3 = 4, S 4 S_4 S4 = 6,···。
现在小明想知道,前 60 个 S i S_i Si 的和是多少?即 S 1 + S 2 + ⋅ ⋅ ⋅ + S 60 S_1 + S_2 + ··· + S_{60} S1+S2+⋅⋅⋅+S60 是多少?
答案提交
这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一
个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。
这也是蓝桥杯国赛的一道真题。参考题解
考虑一下i=12时怎么求解 S i S_i Si,
12 = 6 ∗ 2 12=6*2 12=6∗2 S i = 2 5 ∗ 3 1 S_i=2^5*3^1 Si=25∗31
12 = 3 ∗ 2 ∗ 2 12=3*2*2 12=3∗2∗2 S i = 2 3 ∗ 3 1 ∗ 5 1 S_i=2^3*3^1*5^1 Si=23∗31∗51
12 = 4 ∗ 3 12=4*3 12=4∗3 S i = 2 3 ∗ 3 2 S_i=2^3*3^2 Si=23∗32
12 = 2 ∗ 2 ∗ 3 12=2*2*3 12=2∗2∗3 S i = 2 1 ∗ 3 1 ∗ 5 2 S_i=2^1*3^1*5^2 Si=21∗31∗52
假设数字n的因数个数有12个,那么根据12的分解结果,可以推出来 S i S_i Si的值(每个乘数间1就是要求的数分解后的质数的幂次),但是这个值并不是唯一的,不同的分解结果有不同的值,同一个分解结果不同的幂次分配方式也对应不同的值。比如 12 = 2 ∗ 2 ∗ 3 12=2*2*3 12=2∗2∗3这种分解结果,如果想要n的值比较小,那么就把这几个数字分配到前3小的质数上,即 S i = 2 1 ∗ 3 1 ∗ 5 2 S_i=2^1*3^1*5^2 Si=21∗31∗52。但是这不是最理想的分配方式。我们将3,2,2降序排序,再减一为2,1,1,那么显然 S i = 2 2 ∗ 3 1 ∗ 5 1 S_i=2^2*3^1*5^1 Si=22∗31∗51要比之前求的 S i = 2 1 ∗ 3 1 ∗ 5 2 S_i=2^1*3^1*5^2 Si=21∗31∗52更小。
总结一下这道题的解题步骤,对于 S i S_i Si,我们dfs搜出i所有的分解情况,然后按照刚刚的办法即对分解降序后求出 S i S_i Si。这样一种分解对应一个值,所有的分解对应的值里面求最小值就是 S i S_i Si。
另外,特判因子数为质数,比如因子数是13,减一是12,这个幂次全部分配给2就是我们要找的最小数。
import java.math.BigInteger;
public class Main {
static int n=10000;
static int prime[]=new int[n];
static int index=0;
static BigInteger endBigInteger=new BigInteger("99999999999999999999999999999999999");
public static void main(String[] args) {
int vis[]=new int[n];
for (int i = 2; i <n; i++) {
if (vis[i]==0) {
prime[index]=i;
index++;
for (int j = i*i; j <n; j+=i) {
vis[j]=1;
}
}
}
BigInteger sumBigInteger=new BigInteger("0");
for (int i = 1; i <=60; i++) {
int vis1[]=new int[i+1];
dfs(i,i,0,vis1);
sumBigInteger=sumBigInteger.add(endBigInteger);
endBigInteger=new BigInteger("99999999999999999999999999999999999");
}
System.out.println(sumBigInteger);
}
static void dfs(int Snum,int mid,int start,int vis[]) {
if (mid==1) {
if (endBigInteger.compareTo(loop(Snum, vis))==1) {
endBigInteger=loop(Snum, vis);
}
return;
}
for (int i = 2; i <=mid; i++) {
if (mid%i==0) {
vis[i]++;
dfs(Snum,mid/i, start, vis);
dfs(Snum,mid/i, start+1, vis);
vis[i]--;
}
}
}
static BigInteger loop(int num,int vis[]) {
int vis2[]=new int[vis.length];
for (int i = 0; i < vis.length; i++) {
vis2[i]=vis[i];
}
int index2=0;
BigInteger sumBigInteger=new BigInteger("1");
for (int i =vis2.length-1; i>0; i--) {
if (vis2[i]>0) {
sumBigInteger=sumBigInteger.multiply(new BigInteger(prime[index2++]+"").pow(i-1));
vis2[i]--;
i=i+1;
}
}
return sumBigInteger;
}
}
刚刚的分析是比较正规但是也比较麻烦的思路,这道题还有另外一种讨巧的思路。 S i S_i Si最多由4个质数构成,要使值最小那么这4个质数必然是2,3,5,7。我只需要枚举2,3,5,7对应的幂次就可以了。在枚举的过程中记录当前有t个约数的值,和之前记录的值取一个最小。最后求和输出就行。
import java.util.*;
public class Main {
static int testCount=60;
static int ii=100;
static long result[]=new long[61];
public static void main(String[] args) {
for (int a4 = 0; a4 <= ii; a4++) {
for (int a3 = 0; a3 <= ii; a3++) {
for (int a2 = 0; a2 <= ii; a2++) {
for (int a1 = 0; a1 <= ii; a1++) {
int t=(a1+1)*(a2+1)*(a3+1)*(a4+1);
if(t<=60) {
long single=(long) (Math.pow(2, a1)*Math.pow(3, a2)*Math.pow(5, a3)*Math.pow(7, a4));
if(single<result[t] || result[t]==0) {
result[t]=single;
}
}
}
}
}
}
long sum=0;
for (int i = 1; i <= testCount; i++) {
sum+=result[i];
}
System.out.println(sum);
}
}