蓝桥杯每日一题----唯一分解定理

唯一分解定理

1.内容

任何一个大于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一定是质数。

2.唯一分解定理模板代码

模板代码其实也是唯一分解定理的直接应用,给一个整数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);
}
}
  1. 一般n可能给的很大,注意最好用long类型

  2. 我们如果要求一个数的因数,会从1开始遍历进行试除,那么应该遍历到哪里呢?是n吗?其实遍历到 n \sqrt{n} n 就可以了。因为如果找到了一个因子为a,那么大于 n \sqrt{n} n 的另一个因子就是n/a

  3. 很明显这个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=p1p2,而 p 1 p_1 p1 p 2 p_2 p2一定比q小,那么他们一定会在while里面被除去,因此遍历到q时不再包含它们,自然也不会有q。

  4. 最后一个地方,为什么在代码的最后要加一个if判断呢?还是给大家举一个例子,比如n=396,i=2时,n=99。n=3时,n=11。当i=4时i> n \sqrt{n} n ,所以for循环退出了。但是你也可以发现,11也是n的一个质因子,所以我们在最后要判断一下,防止把这种情况漏了。

练习题目

3.应用

(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=p0j0p1j1p2j2...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} 233253来说,可以选择一个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=p0j0p1j1p2j2...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)

4.进阶题目

阶乘约数

问题描述
定义阶乘 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=p0j0p1j1p2j2...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!=1234...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 是多少?
答案提交
这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一
个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。

题目分析1

这也是蓝桥杯国赛的一道真题。参考题解

考虑一下i=12时怎么求解 S i S_i Si

12 = 6 ∗ 2 12=6*2 12=62 S i = 2 5 ∗ 3 1 S_i=2^5*3^1 Si=2531

12 = 3 ∗ 2 ∗ 2 12=3*2*2 12=322 S i = 2 3 ∗ 3 1 ∗ 5 1 S_i=2^3*3^1*5^1 Si=233151

12 = 4 ∗ 3 12=4*3 12=43 S i = 2 3 ∗ 3 2 S_i=2^3*3^2 Si=2332

12 = 2 ∗ 2 ∗ 3 12=2*2*3 12=223 S i = 2 1 ∗ 3 1 ∗ 5 2 S_i=2^1*3^1*5^2 Si=213152

假设数字n的因数个数有12个,那么根据12的分解结果,可以推出来 S i S_i Si的值(每个乘数间1就是要求的数分解后的质数的幂次),但是这个值并不是唯一的,不同的分解结果有不同的值,同一个分解结果不同的幂次分配方式也对应不同的值。比如 12 = 2 ∗ 2 ∗ 3 12=2*2*3 12=223这种分解结果,如果想要n的值比较小,那么就把这几个数字分配到前3小的质数上,即 S i = 2 1 ∗ 3 1 ∗ 5 2 S_i=2^1*3^1*5^2 Si=213152。但是这不是最理想的分配方式。我们将3,2,2降序排序,再减一为2,1,1,那么显然 S i = 2 2 ∗ 3 1 ∗ 5 1 S_i=2^2*3^1*5^1 Si=223151要比之前求的 S i = 2 1 ∗ 3 1 ∗ 5 2 S_i=2^1*3^1*5^2 Si=213152更小。

总结一下这道题的解题步骤,对于 S i S_i Si,我们dfs搜出i所有的分解情况,然后按照刚刚的办法即对分解降序后求出 S i S_i Si。这样一种分解对应一个值,所有的分解对应的值里面求最小值就是 S i S_i Si

另外,特判因子数为质数,比如因子数是13,减一是12,这个幂次全部分配给2就是我们要找的最小数。

题目代码1

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;
    }
}

题目分析2

刚刚的分析是比较正规但是也比较麻烦的思路,这道题还有另外一种讨巧的思路。 S i S_i Si最多由4个质数构成,要使值最小那么这4个质数必然是2,3,5,7。我只需要枚举2,3,5,7对应的幂次就可以了。在枚举的过程中记录当前有t个约数的值,和之前记录的值取一个最小。最后求和输出就行。

题目代码2

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);
    }
}

你可能感兴趣的:(蓝桥杯,java,算法)