一、欧几里得算法(辗转相除法)
最小公倍数=m * n/gcd(m,n)
public class Euclid {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int m = sc.nextInt();
int n = sc.nextInt();
int res = gcd(m, n);
System.out.println(res);
//递推
for (; ; ) {
if (m == 0) {
System.out.println(n);
break;
}
int t = m;
m = n % m;
n = t;
}
}
// 递归 最大公因数
public static int gcd(int m, int n) {
return (n == 0) ? m : gcd(n, m % n);
}
// 最小公倍数
public static int lcm(int m,int n){
return m*n/gcd(m,n);
}
}
二、裴蜀(贝祖)等式(扩展欧几里得)
-
对任何整数a,b和它们的最大公因数d,关于未知数x和y的线性丢番图方程(称为裴蜀等式): ax+by=m有整数解时当且仅当m是d的倍数 -->ax+by=n * gcd(a,b)
-
裴蜀等式有解时必然有无穷多个整数解,每组解x、y都称为裴蜀数, 可用扩展欧几里得算法求得。
-
方程12x+42y=6有解
-
特别的ax+by=1有整数解当且仅当整数a和b互素
-
扩展欧几里得算法就是在求a,b的最大公约数m=gcd(a,b)的同时,求出贝祖等式ax+by=m的一个解(x0,y0)
-
如何递推?
x = y1
y = x1-a/b*y1 -
通解:
x=x0+(b/gcd) * t 所有的x对b同模
y=y0-(a/gcd) * t 所有的y对a同模 -
如果想要得到x大于0的第一个解?
b/=d; x=(x0%b+b)%b
gcd(a,b)
return b==0?a
我们观察到:欧几里得算法停止的状态是:a=gcd,b=0,那么,这是否能给我们求解x y提供一种思路呢?
a'x + b'y = gcd 此时x=1,y为任意数
因为,这时候,只要a=gcd的系数是1,那么只要b的系数是0或者其他值(无所谓是多少,反正任何数乘以0都等于0但是a的系数一定要是1),这时,我们就会有:a*1+b*0=gcd
当然这是最终状态,但是我们是否可以从最终状态反推到最初的状态?
假设当前我们要处理的是求出a和b的最大公约数,并求出x和y使得a*x+b*y=gcd式子(1),
而我们已经求出了下一个状态:b,和a%b的最大公约数,并且求出了一组x1和y1,使得:b*x1+(a%b)*y1=gcd式子(2),---下一个状态
那么这两个相邻的状态之间是否存在一种关系呢?
a%b=k ---> a=b*(a/b)+k --->k=a-(a/b)*b
我们知道:a%b=a-(a/b)*b,那么我们可以进一步得到:
gcd=b*x1+(a-(a/b)*b)*y1=b*x1+a*y1-(a/b)*b*y1=a*y1+b*(x1-a/b*y1)式子(3)
对此之前我们的状态,式子3和式子1:求一组x和y使得:a*x+b*y=gcd,是否发现了什么?
这里:
x=y1
y=x1-a/b*y1
这就是递归式,注意x,y是递归过程中的上一层,x1,y1是下一层(下一个状态)得到的值
2X+7Y=1 aX+bY=1
(b,a%b):左下右上分析
(2,7) ->x=y1=-3 y=x1-a/b*y1=1
->(7,2) ->x=y1=1 y=x1-a/b*y1=-3
->(2,1) ->x=y1=0 y=x1-a/b*y1=1 x-->新x1,新y1
->(1,0) ->x1=1,y1=0
public class PeiZuEquation {
static long x;
static long y;
public static void main(String[] args) {
try {
linearEquation(2, 7, 1);
System.out.println(x + " " + y);
} catch (Exception e) {
System.out.println("无解");
}
}
/**
* 扩展欧几里得
* 调用完成后xy是ax+by=gcd(a,b)的解
*
* @param a x的系数
* @param b y的系数
* @return
*/
public static long ext_gcd(long a, long b) {
if (b == 0) {
x = 1;
y = 0;
return a; // 最大公约数
}
long res = ext_gcd(b, a % b);
// x,y已经被下一层递归更新了
long x1 = x; // 备份x
x = y;//更新x
y = x1 - a / b * y; // 更新y
return res;
}
/**
* 线性方程
* ax + by = m 当m是gcd(a,b)倍数时有解
* 等价于ax = m mod d
*/
public static long linearEquation(long a, long b, long m) throws Exception {
long d = ext_gcd(a, b);
// m不是gcd(a,b)的倍数,这个方程无解
if (m % d != 0) {
throw new Exception("无解");
}
long n = m / d; // 约一下,考虑m是d的倍数
x *= n;
y *= n;
return d;
}
}
三、一步之遥
从昏迷中醒来,小明发现自己被关在x星球的废矿车里。矿车停在平直的废弃的轨道上。他的面前是两个按钮,分别写着"F"和"B"。
小明突然想起,这两个按钮可以控制矿车在轨道上的前进和后退。按F会前进97米,按B会后退127米。透过昏暗的灯光,小明看到自己的前方1米元正好有个监控探头。他必须设法使得矿车正好停在摄像头的下方,才有机会争取同伴的援助。或许,通过多次操作F和B可以办到。
矿车上的动力已经不足,黄色的警示灯在摸摸闪烁…每次进行F或B操作都会消耗一定的能量。小明飞快的计算,至少要多少次操作,才能把矿车准确地停在前方1米远的地方。
请填写为了达成目标,最少需要操作的次数。
97x-127y=1
ax+by=m
暴力解法:双重for循环
public class OneStepAway {
static long x;
static long y;
public static void main(String[] args) {
try {
linearEquation(97, 127, 1);
System.out.println(abs(x) + abs(y));
} catch (Exception e) {
System.out.println("无解");
}
}
/**
* 扩展欧几里得
* 调用完成后xy是ax+by=gcd(a,b)的解
*
* @param a x的系数
* @param b y的系数
* @return
*/
public static long ext_gcd(long a, long b) {
if (b == 0) {
x = 1;
y = 0;
return a; // 最大公约数
}
long res = ext_gcd(b, a % b);
// x,y已经被下一层递归更新了
long x1 = x; // 备份x
x = y; // 更新x
y = x1 - a / b * y; // 更新y
return res;
}
/**
* 线性方程
* ax+by=m 当m是gcd(a,b)倍数时有解
* 等价于ax=m mod d
*/
public static long linearEquation(long a, long b, long m) throws Exception {
long d = ext_gcd(a, b);
// m不是gcd(a,b)的倍数,这个方程无解
if (m % d != 0) {
throw new Exception("无解");
}
long n = m / d; // 约一下,考虑m是d的倍数
x *= n;
y *= n;
return d;
}
}
四、素数
n是不是素数?
- 2~n-1间是否有整数能整除n
- 如果d是n的约数,那么n/d也是n的约数,由n=d*(n/d)可知,d<=根号n,所以检查2~根号n之间是否有整数能整除n
public class Prime {
public static void main(String[] args) {
System.out.println(isPrime(5));
// 求出第10万个素数
// 基础做法
int cnt=0;
long x=2;
while (cnt<100000){
if (isPrime(x)){ // 判断是否为素数
cnt++;
}
x++;
}
System.out.println(x-1);
// 优化
m(100000);
}
// 判断是否为素数
public static boolean isPrime(long num) {
for (int i = 2; i * i <= num; i++) {
if (num % i == 0) {
return false;
}
}
return true;
}
// 埃氏筛法(蓝桥杯)
// 求第10万个素数
public static void m(int N) {
// N是第N个素数
// 已知在整数x内大概有x/log(x)个素数
// 现在我们要逆推:要想求第N个素数,我们的整数范围是什么
// length就是整数范围
int n = 2;
// 素数定理
while (n / Math.log(n) < N) {
n++;
}
// 开辟一个数组,下标是自然数,值是标记
// 基本思路是筛选法,把非素数标记出来
int[] arr = new int[n];
int x = 2;
while (x < n) {
if (arr[x] != 0) { // 标记过了,继续下一个
x++;
continue;
}
int k = 2;
// 对每个x我们从2倍开始,对x的k倍标记为-1
while (x * k < n) { // 素数的倍数不是素数 标记为-1
arr[x * k++] = -1;
}
x++;
}
// 筛选完后,这个很长的数组里面非素数下标对应的值都是-1
int sum=0;
for (int i = 2; i < arr.length; i++) {
// 是素数,计数+1
if (arr[i] == 0) {
sum++;
}
if (sum==N){
System.out.println(i);
return;
}
}
}
}
五、质因数分解
public class Prime_Factor {
public static void main(String[] args) {
Map<Integer, Integer> map = primeFactor(600); // 求质因数
System.out.println(map);
}
// 质因数分解8=2*2*2
public static Map<Integer, Integer> primeFactor(int num) {
HashMap<Integer, Integer> map = new HashMap<>();
for (int i = 2; i * i <= num; i++) {
while (num % i == 0) {
Integer v = map.get(i);
if (v == null) {
map.put(i, 1);
} else {
map.put(i, v + 1);
}
num /= i;
}
}
return map;
}
}
六、快速幂运算
public class Power {
public static void main(String[] args) {
System.out.println(ex(2, 5));
System.out.println(ex2(2, 5));
}
//反复平方
public static int ex(int a, int n) {
if (n == 1) {
return a;
}
int temp = a; // a的当前次方次方
int res = 1;
int exponent = 1;
while ((exponent << 1) < n) {
temp *= temp;
exponent <<= 1;
}
res *= ex(a, n - exponent);
return res * temp; // n-exponent(剩余的)*temp(之前的)
}
//巧算 二进制
public static long ex2(int a, int n) {
long pingFangShu = a; // a的一次方
long res = 1;
while (n != 0) {
// 遇1累乘现在的幂
if ((n & 1) == 1) {
res *= pingFangShu;
}
// 每移动一次,幂累乘方一次
pingFangShu = pingFangShu * pingFangShu;
// 右移移位
n >>= 1;
}
return res;
}
}
七、Nim游戏
一共有N堆石子,编号1…n,第i堆中有a[i]个石子。每一次操作Alice和Bob可以从任意一堆石子中取出任意数量的石子,至少取一颗,至多取出这一堆剩下的所有石子。两个人轮流行动,取光所有石子的一方获胜。Alice为先手,给定a,假设两人都采用最优策略,谁会获胜?
结论:每堆中的石子做异或,异或结果为0则表示取完,也就是说,当第一次面对的异或结果不为0,且每次采取最优策略,必胜。
先手:非0,赢;0,输
public class NimGame {
public static void main(String[] args) {
int[] arr = {3, 4, 5}; // 三堆 分别为3,4,5个石子
boolean res = solve(arr);
System.out.println(res);
}
public static boolean solve(int[] arr) {
int res = 0;
for (int i = 0; i < arr.length; i++) {
res ^= arr[i];
}
return res != 0;
}
}
八、天平称重问题
天平称重:变种三进制
用天平称重时,我们希望用尽可能少的砝码组合称出尽可能多的重量。如果有无限个砝码,但它们的重量分别是1,3,9,27,81,…等3的指数幂神奇之处在于用它们的组合可以称出任意整数的重量(砝码允许放在左右两个盘中)。
本题目要求编程实现:对用户给定的重量,给出砝码组合方案,重量<1000000
例如:
用户输入:
5
程序输出:
9-3-1
* 天平称重 递归解法 规律
* 当前x是否比砝码的1/2大决定取比当前大一点的砝码还是小一点的砝码
* 1:1
* 2:3-1
* 3:3
* 4:3+1
* 5:9-3+1
* ...
* 进制解法 余1--取 余0--不取 -1--取 余2改为-1
* 19/3=6...1
* 6/3=2....0
* 2/3=0....2 --> 2/3=1....-1
* 1/3=0....1
* 1 0 -1 1 <--> 1 3 9 27
public class BalanceWeight {
public static void main(String[] args) {
for (int i = 1; i < 20; i++) {
System.out.println(i + ":" + f(i));
}
for (int i = 1; i < 20; i++) {
System.out.println(i + ":" + f1(i));
}
}
// 递归求解
public static String f(int x) {
int a = 1;
while (a < x) a *= 3;
if (a == x) return "" + a; // 恰好等于 3 的指数幂
if (x <= a / 2) { // 取前一个 剩余的递归求解
return a / 3 + "+" + f(x - a / 3);
}
// 取后一个(需要变符号) 剩余的由前面的凑
return a + revel(f(a - x));
}
// 正负变号
private static String revel(String s) {
s = s.replace("-", "#");
s = s.replace("+", "-");
s = s.replace("#", "+");
return "-" + s;
}
// 进制解法 变种三进制
public static String f1(int x) {
String s = "";
int q = 1; // 权重
while (x > 0) {
int sh = x / 3; // 商
if (x % 3 == 1) {
s = "+" + q + s;
} else if (x % 3 == 2) {
sh++;
s = "-" + q + s;
}
x = sh;
q *= 3;
}
return s.substring(1);
}
}