重点: 数学知识:
1.数论-试除法
:最简单的!
- 质数/素数
合数是指在大于1的整数中除了能被1和本身整除外,还能被其他数(0除外)整除的数。与之相对的是质数,而1既不属于质数也不属于合数。最小的合数是4。
-
质数/素数
定义:从2开始
只包含1和本身的这两个约数
质数的判定
两个条件:
<2 false
% !=0
缺点:
约数成对存在的,变小时间。
开平方 - 慢
i*i<=n
简单化简-试除法
a|b--b除以a=整数,余数为0,b为被除数,a为除数
说明
d|n--n除以d=整数,余数为0.
(n/d)|n--n除以(n/d)=整数,余数为0.
例子:
n=8,d=4
d|n
4|8 = 2
(n/d)|n
(8/4)|8=4
这两个数是成对出现的,所以可以进行简化
这个可以替代循环里面的,节省时间
暴力版:
boolean is_prime(int x){
if}
boolean is_prime(int x){
if(x<2)return false;
for(int i=2;i<x/i;i++){
if(x%i==0)return false;
}
}
时间复杂度o跟下n
代码:
package 数学知识;
public class 质数的判定 {
//暴力解法
public static boolean is_prime1(int x){
if(x<2) return false;
for(int i=2;i<x;i++){
if(x%i==0){
return false;
}
}
return true;
}
//稍微简化--证明法:合数:N,存在M,使得2<=M<=根下N
public static boolean is_prime2(int x){
if(x<2) return false;
//根据 质数的乘积是成对出现的所以,
//2<=M<根下N
for(int i=2;i<=Math.sqrt(x);i++){
if(x%i ==0)return false;
}
return true;
}
//更简化版
public static boolean is_prime3(int x){
if(x<2)return false;
//M|N,N/M|N,所以直接省略一个除数和判断
for(int i=1;i<=x/i;i++){
if(x%i==0)return false;
}
return true;
}
public static void main(String[] args) {
int x=8;
if(is_prime3(x)) System.out.println("质数");
else System.out.println("不是质数");
}
}
- 分解质因数
分解质因数的时候还用到了一个没讲的结论:
任何数都可以表示成质数的乘积。
—分解质因数与质因数不一样,分解质因数是一个过程,而质因数是一个数。
原理:
根据算术基本定理,不考虑排列顺序的情况下,每个正整数都能够以唯一的方式表示成它的质因数的乘积。
n=p1^a1 * p2^a2 *p3^a3.....pn^an
比如一个数16 在分解时先找到2这个质因子,
然后由于16/2后还可以/2,
所以会在2这个质因子上产生次方 2^4=16
比如说12:
i=2;
12/2=6,6/2=3, //2^2
3/3=1//3^1
所以12可以被分解为
2^2*3^1=12
不优化版本:从2~n 找到能整除的因子然后算次方
原理2:
n中最多只含有一个大于sqrt(n)的因子。
证明通过反证法:如果有两个大于sqrt(n)的因子,那么相乘会大于n,矛盾。证毕
于是我们发现最多只有一个大于sqrt(n)的因子,对其进行优化。
先考虑比sqrt(n)小的,代码和质数的判定类似
最后如果n还是>1,说明这就是大于sqrt(n)的唯一质因子,输出即可
找到这些数和幂
代码:
package 数学知识;
import java.util.Scanner;
/*前提知识:
1.从2开始找第一个质数,找到后使劲除
2.每个数只有一个大于它本身开平方的数*/
public class 分解质因数 {
//暴力法
public static void divide1(long x){
for(int i=2;i<=x;i++){
if(x % i==0){
int s=0;
while (x%i==0){//8%2=0//4%2==0//2%2=0//\
x = x/i;//8=4//4=2//2=1
s++;//s=1//s=2//s=3
}
System.out.println(i+" "+s);
}
}
System.out.println();
}
public static void divide2(long x){
//首先质因数肯定是一个质数-因此更改边界
for(int i=2;i<=x/i;i++){//从2开始往后做除法,第一个能被除开的数一定是原数n的一个质数
if(x%i==0){//x中已经不包含2-x-1任何的质因子了,一直再除,x=x/i
//i中已经不包含任何质因子了,i一开始已经是质数了
int s =0;
while (x%i==0){
x = x/i;
s++;
}
System.out.println(i+" "+s);
}
}
if(x>1)System.out.println(x+" "+1);//x 的质因子最多只包含一个大于 根号x 的质数。如果有两个,这两个因子的乘积就会大于 x,矛盾
}
public static void main(String[] args) {
//数字使用longlong,n使用整形
Scanner sc = new Scanner(System.in);
//int n = sc.nextInt();
int n=2;
long x = 6;
divide2(x);
}
}
筛质数:
- 简单版暴力版
给定数n
1-n中有几个质数,先判断,算个数。
package 数学知识;
import java.util.Scanner;
public class 筛质数 {
public static boolean is_prime(long x){
if(x<2) return false;
for(int i=2;i<=x/i;i++){
if (x%i==0)return false;
}
return true;
}
//给定一个正整数 n,请你求出 1∼n 中质数的个数。
public static void shai(long n){
//1-n
int s =0;
for(long i=2;i<=n;i++){
if(is_prime(i)){
s++;
}
}
System.out.println(s);
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
long n =8;
shai(n);
}
}
- 朴素筛法
不管出现哪个数,都进行给他加然后就可以消掉所有的,不过进行了很多重复性操作。
package 数学知识;
import java.util.Scanner;
public class 筛质数2 {
static int N =100010;
static boolean[] st = new boolean[N];
static int[] primes = new int[N];
static int n;
public static void get_prime1(){
int num=0;
//素数和合数都筛
for(int i=2;i<=n;i++){
if(st[i] == false) primes[num++] =i;//注意这里没有大括号
//就算来了一个4上面不执行下面也会执行
for(int j=i;j<=n;j=j+i){//4,8,12
st[j] =true;
}
}
System.out.println(num);//num从0开始个数刚好是num++
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
get_prime1();
}
}
- 埃式筛法:
只筛质数-因为合数也是由质数得来的
1.每次一找到一个数判断st,false说明现在还没访问,-放进数组-再进行循环判断数组中有没有这个的倍数的数据
package 数学知识;
import java.util.Scanner;
public class 筛质数2 {
static int N =100010;
static boolean[] st = new boolean[N];
static int[] primes = new int[N];
static int n;
public static void get_prime(){
int num=0;
for(int i=2;i<=n;i++){
if(st[i]==false) primes[num++] = i;
for(int j=i;j<=n;j=j+i){
st[j] = true;
}
}
System.out.println(num);
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
get_prime();
}
}
线性筛法=n只会被最小质因数筛掉
代码:
package 数学知识;
import java.util.Scanner;
public class 筛质数线性筛法 {
static int N =100010;
static boolean[] st = new boolean[N];
static int[] primes = new int[N];
static int n;
static int num;
public static void get_primes(int n){
for(int i=2;i<=n;i++){
if(st[i]==false) primes[num++] = i;
//每次用最小的去筛
for(int j=0;primes[j]*i<=n;j++){//找到ihej
st[primes[j]*i]=true;
if(i%primes[j] == 0) break;
//break 是为了后面减少重复筛选。
//举个例子,对于一个数9,92=18将18标记为合数,循环继续;
// 93=27将27标记为合数,此时发现9%3=0,循环退出。
// 如果将循环继续下去会出现筛除95=45的情况,
// 而45=153,在15时会被在筛去一次,故不可行
}
}
System.out.println(num);
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
get_primes(n);
}
}
线性筛法比较快。
约数
求所有约数
定义:
约数,又称因数。
整数a除以整数b(b≠0) 除得的商正好是整数而没有余数,我们就说a能被b整除,或b能整除a。
a称为b的倍数,b称为a的约数。
一个数的约数也是整对出现的。
试除法求所有约数
超级暴力法
package 数学知识;
//输入样例:
//2
//6
//8
//输出样例:
//1 2 3 6
//1 2 4 8
import java.util.Scanner;
public class 试除法求约数 {
public static void yueshu(int n){
for(int i=1;i<=n;i++){
if(n%i==0){
System.out.print(i+ " ");
}
}
}
public static void main(String[] args) {
Scanner sc =new Scanner(System.in);
int n= sc.nextInt();
for(int i=0;i<n;i++){
int x = sc.nextInt();
yueshu(x);
System.out.println();
}
}
}
第二种方法:
数组使用array.sort进行排序
因为数据是成对出现的因此查找的时候可以省略
import java.util.Arrays;
import java.util.Scanner;
public class Main {
static int N =1000;
public static void yueshu(int n){
int[] res = new int[1000];
int num = 0;
for(int i=1;i*i<=n;i++){
if(n%i==0){
res[num] = i;
num++;
if(i != n/i){
res[num] = n/i;
num++;
}
}
}
Arrays.sort(res,0,num);
for(int i=0;i<num;i++){
System.out.print(res[i] + " ");
}
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
while (n-->0){
int x = sc.nextInt();
yueshu(x);
System.out.println();
}
}
}
第三种方法:
使用linklist进行存储。
因为是集合使用collection进行排序
package 数学知识;
//输入样例:
//2
//6
//8
//输出样例:
//1 2 3 6
//1 2 4 8
import java.util.*;
public static void yueshu2(int n){
ArrayList<Integer> res = new ArrayList<>();
//因为约数也是成对出现的,除了完全平方数是一个 例如4 -2
//所以只找一半就可以找到所有值。
for(int i=1;i*i<=n;i++){
if(n%i==0){
//首先i肯定是约数一个,如果是成对出现的则
res.add(i);
if(n/i != i){
res.add(n/i);
}
}
}
//加完所有的数之后输出的时候按照顺序
Collections.sort(res);
for(int i:res){
System.out.print(i+ " ");
}
}
public static void main(String[] args) {
Scanner sc =new Scanner(System.in);
int n= sc.nextInt();
for(int i=0;i<n;i++){
int x = sc.nextInt();
yueshu2(x);
System.out.println();
}
}
}
求约数个数:
n/d==0 推出 d能整除n 记做 d|n ,d称为n的约数。
求约数个数用的算数基本定理
算数基本定理用到质因子分解
因此约数个数间接用到质因子分解
算数基本定理
代码
package 数学知识;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
public class 试除法求约数个数2 {
static int N=1000010;
//乘法答案比较长 lonh
static long res = 1;//cheng 1
static HashMap<Integer,Integer> map = new HashMap<>();
static int n;
public static void yueshu_num(int n){
//明白一个道理:算法原理由质因子分解而来,而计算约数个数,需要用到算法原理,则间接使用到质因子分解
for(int i=2;i*i<=n;i++){
while (n%i==0){
n=n/i;
map.put(i,map.getOrDefault(i,0)+1);
}
}
if(n>1) map.put(n,map.getOrDefault(n,0)+1);
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
while (n-->0){
int x = sc.nextInt();
yueshu_num(x);
}
//shuchu
for(Map.Entry<Integer,Integer> map:map.entrySet()){
res = (res * (map.getValue()+1))%N;
}
System.out.print(res);
}
}
算数基本定理:
算术基本定理可表述为:
任何一个大于1的自然数 N,如果N不为质数,
那么N可以唯一分解成有限个质数的乘积N=P1^a1P2^a2P3^a3......Pn^an,
这里P1<P2<P3......<Pn均为质数,
其中指数ai是正整数。
这样的分解称为 N 的标准分解式。
最早证明是由欧几里得给出的,由陈述证明。
此定理可推广至更一般的交换代数和代数数论。
约数/因数
“约数也叫做因数,是因数的另一个称呼”
约数个数根据公式可以:
约数之和:
约数之和:用到的定理:也是和约数个数是一样的,
算法分析
基本思想:
如果 N=p1c1∗p2c2∗…∗pkckN=p1c1∗p2c2∗…∗pkck
约数个数:(c1+1)∗(c2+1)∗…∗(ck+1)(c1+1)∗(c2+1)∗…∗(ck+1)
约数之和: (p10+p11+…+p1c1)∗…∗(pk0+pk1+…+pkck)(p10+p11+…+p1c1)∗…∗(pk0+pk1+…+pkck)
while (b -- ) t = (t * a + 1) % mod;
t=t∗p+1t=t∗p+1
t=1t=1
t=p+1t=p+1
t=p2+p+1t=p2+p+1
……
t=pb+pb−1+…+1
因为公式就是
while (b -- ) t = (t * a + 1) % mod;
每次都可以扩展成这样
展开就可以解决,发现是所有的排列组合
代码:
注意事项:
边界值涉及到连乘的时候,就是有问题的时候,一版用long结构进行定义
package 数学知识;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
//给定 n 个正整数 ai,请你输出这些数的乘积的约数之和,答案对 109+7 取模。
public class 约数之和 {
static int mod = 1000000007;
static long res = 1;//cheng 1
static HashMap<Integer,Integer> map = new HashMap<>();
static int n;
public static void yueshu_num(int n){
//明白一个道理:算法原理由质因子分解而来,而计算约数个数,需要用到算法原理,则间接使用到质因子分解
for(int i=2;i*i<=n;i++){
while (n%i==0){
n=n/i;
map.put(i,map.getOrDefault(i,0)+1);
}
}
if(n>1) map.put(n,map.getOrDefault(n,0)+1);
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
while (n-->0){
int x = sc.nextInt();
yueshu_num(x);
}
//map的方式进行简单公式遍环
//t = (t*p+1)%mod
for(Map.Entry<Integer,Integer> map :map.entrySet()){
long t=1;//这个数很大
int p = map.getKey();
int n = map.getValue();
while (n-->0){
t = (t*p+1)%mod;
}
res = (res*t)%mod;
}
System.out.print(res);
}
}
辗转相除法
为甚对?
//辗转相除法:
//d为a,b的公约数
//则 d 为(b,a%b)的公约数
//证明:
//a%b = a-(a/b)b=a-cb
//d|a,d|b--> d|a-kb--->d|b-(a%b)
//因为d|b-a%b,-->a|b-a%b+a%b-->d|b
//所以d为约数
gcd非常快速的。
import java.util.Scanner;
//辗转相除法:
//d为a,b的公约数
//则 d 为(b,a%b)的公约数
//证明:
//a%b = a-(a/b)b=a-cb
//d|a,d|b--> d|a-kb--->d|b-(a%b)
//因为d|b-a%b,-->a|b-a%b+a%b-->d|b
//所以d为约数
public class Main {
public static int gcd(int a,int b){
return b!=0 ?gcd(b,a%b):a;
}
public static int gcd2(int a,int b){
if(b==0)return a;
return gcd(b,a%b);
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
while (n-->0){
int a = sc.nextInt();
int b = sc.nextInt();
//gcd(a,b);
System.out.println(gcd2(a,b));
}
}
}