编程与数学问题

5 篇文章 0 订阅
5 篇文章 1 订阅

一、欧几里得算法(辗转相除法)

在这里插入图片描述

最小公倍数=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);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

明仔爱编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值