第六章 数学问题(上)

1、巧用进制

用天平称重时,我们希望用尽可能少的砝码组合称出尽可能多的重量。
如果只有5个砝码,重量分别是1,3,9,27,81
则它们可以组合称出1到121之间任意整数重量(砝码允许放在左右两个盘中)。


本题目要求编程实现:对用户给定的重量,给出砝码组合方案。
例如:
用户输入:
5
程序输出:
9-3-1
用户输入:
19
程序输出:
27-9+1


要求程序输出的组合总是大数在前小数在后。
可以假设用户的输入的数字符合范围1~121。

package org.lanqiao.algo.elementary._06_math;

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;


public class Case01_天平称重 {
  public static void main(String[] args) {
    System.out.println(Integer.toString(1000000, 3));
      Scanner scanner = new Scanner(System.in);
      int N = scanner.nextInt();
    // m1(N);
    //转成3进制
    final String x = Integer.toString(N, 3);
    //翻转后转成字符数组
    char[] arr = new StringBuilder(x).reverse().toString().toCharArray();
    //容器放处理之后的0 -1 1
    List<Integer> list = new ArrayList<>();
    for (int i = 0; i < arr.length; i++) {
      if (arr[i] == '2') {
        list.add(0, -1);//-1插在开头
        if (i == arr.length - 1) {
          list.add(0, 1);//最后一个字符,进位
        } else {
          ++arr[i + 1];//否则,对下一个数字加1
        }
      } else if (arr[i] == '3') {
        list.add(0, 0);//插入0
        //更高位进1
        if (i == arr.length - 1) {
          list.add(0, 1);
        } else {
          ++arr[i + 1];
        }
      } else {
        list.add(0, arr[i] - '0');
      }
    }
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < list.size(); i++) {
      if (list.get(i) == 1) sb.append("+").append((int) Math.pow(3, list.size() - i - 1));
      if (list.get(i) == -1) sb.append("-").append((int) Math.pow(3, list.size() - i - 1));
    }
    System.out.println(sb.substring(1));
  }
 //暴力解法
  private static void m1(int n) {
    int[] s = {0, 1, -1};
    for (int a = 0; a < 3; a++) {
      for (int b = 0; b < 3; b++) {
        for (int c = 0; c < 3; c++) {
          for (int d = 0; d < 3; d++) {
            for (int e = 0; e < 3; e++) {
              if (s[a] * 81 + s[b] * 27 + s[c] * 9 + s[d] * 3 + s[e] * 1 == n) {
                // System.out.println(s[a] + "*81+" + s[b] + "*27+" + s[c] + "*9+" + s[d] + "*3+" + s[e] + "*1");
                // return;
                StringBuilder sb = new StringBuilder();
                if (s[a] == 1) sb.append("81");
                if (s[b] == 1) sb.append("+27");
                if (s[b] == -1) sb.append("-27");
                if (s[c] == 1) sb.append("+9");
                if (s[c] == -1) sb.append("-9");
                if (s[d] == 1) sb.append("+3");
                if (s[d] == -1) sb.append("-3");
                if (s[e] == 1) sb.append("+1");
                if (s[e] == -1) sb.append("-1");
                if (sb.charAt(0) == '+' || sb.charAt(0) == '-')
                  System.out.println(sb.substring(1));
                else
                  System.out.println(sb.toString());
                return;
              }
            }
          }
        }
      }
    }
  }
}

2、Nim游戏:(有一点点博弈的感觉)

一共有N堆石子,编号为1到n,,第i堆有a【i】个石子,每一次俩人从任意一堆中取出至少一颗石子,最多取走这一堆剩下的所有石子,两个人轮流行动,取光所有石子的一方获胜,,a为先手,,给定a,假设两个人都辞去最优策略,谁会获胜?

思路: 比如 3 4 5

写成二进制全部亦或起来,,如果为0的话,都能把他弄成不为0

如果不为0,我总有办法把他变成0,比如说我在任意一堆中拿走一个石子,,结果就变成0

所以 先手:1、面临非0,这样我就能把他变成0

                           2、面临0,,必输

class Test{
    public static void main(String[] args) {
        int []A = {3,4,5};
        boolean res = solve(A);
        System.out.println(res);
    }
    static boolean solve(int [] A){
        int res =0;
        for(int i=0;i<A.length;i++){
            res^=A[i]; //亦或,,相同为1 不同为0
        }
        return res !=0;
    }
}

3、阶梯尼姆问题:

题目链接:http://poj.org/problem?id=1704

 * Sample Input
 2
 3
 1 2 3
 8
 1 5 6 7 9 12 14 17
 Sample Output
 Bob will win
 Georgia will win

import java.util.Arrays;
import java.util.Scanner;

class Test48{
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int casenum = sc.nextInt();
        int [][]data = new int[casenum][];
        for(int i=0;i<casenum;i++){
            int k=sc.nextInt();
            data[i] = new int[k];
            for(int j=0;j<k;j++){
                data[i][j] = sc.nextInt();
            }
        }
        for(int i=0;i<casenum;i++){
            String res = deal(data[i]);
            System.out.println(res);
        }
    }

    static String deal(int [] A){
        int len = A.length;
        Arrays.sort(A);
        int res = 0;
        if((len&1)==1){  //按位与运算 是奇数
            for(int i=0;i<len;i+=2){
                res^=(i==0)?(A[0] -1):(A[i] - A[i-1] -1);//把两个位置之间当成石子数,个数等于后面减前面-1
            }
        }else {//偶数的话
            for(int i=1;i<len;i+=2){
                res^=(A[i] - A[i-1] -1);

            }
        }
        if(res ==0){
            return "Bob will win";
        }else {
            return "Gorina will win";
        }
    }
}

4、求和公式:

5、欧几里得算法:(其实就是辗转相除法求最大公因子)

static long gcd(int m,int n){
        return n==0?m:gcd(n,m%n);
    }
static long lcm(long a, long b) {
    return a * b / gcd(a, b);
  }

6、欧几里得扩展算法:(裴蜀等式)

ax+by = m (x和y为未知数,)此方程称为裴蜀等式,这个方程要想有解,m必须为d的倍数(d是a和b的最大公约数)

这个等式有无穷多个整数解,每组x,y都称为裴蜀数,可用扩展欧几里得算法求得,

化简:方程 ax+by=1有整数解当且紧当a和b互素

!官方词条:

裴蜀定理(或贝祖定理,Bézout's identity)得名于法国数学家艾蒂安·裴蜀,说明了对任何整数a、b和它们的最大公约

数d,关于未知数x和y的线性不定方程(称为裴蜀等式):若a,b是整数,且gcd(a,b)=d,那么对于任意的整数x,y,ax+by都一定是d的倍数,特别地,一定存在整数x,y,使ax+by=d成立。

它的一个重要推论是:a,b互质的充要条件是存在整数x,y使ax+by=1.

 

推导过程:

最后一行有水印看不清楚: x = (x0%b+b)%b

//我这里用private static class ext 因为竞赛中可能一个大Main类,不允许有其他类,所以此处采用私有的内部静态类
class test3{
    public static void main(String[] args) {
        try{
            ext.linearequation(2,3,1);
            System.out.println(ext.x+" "+ext.y);
        }catch (Exception e){
            System.out.println("无解");
        }
    }
    private static class ext{
        static long x;
        static long y;
        //扩展欧欧几里得
       // 调用后 x和y是ax+by=gcd(a,b)的解,这个解是上面求得的x和y再乘m比d的数字
        static long ext_gcd(long a,long b){
            if(b==0){
                x=1;
                y=0;
                return a;
            }
            long res = ext_gcd(b,a%b);
            long x1 = x; //备份x
            x = y;  //4
            y = x1-a/b*y; //更新y
            return res;
        }

        //线性方程 
        //ax+by = m 当m为gcd(a,b)的倍数时,有解

        static long linearequation(long a,long b,long m) throws Exception{
            long d = ext_gcd(a,b);
            //如果m不是d的倍数 ,就无解
            if(m%d !=0){
                throw new Exception(m+"%"+"gcd("+a+","+b+")"+"!=0 ~无解");
            }
            long n = m/d;
            x*= n;
            y*=n;
            return d;
        }
    }
}

例题:

一步之遥

从昏迷中醒来,小明发现自己被关在X星球的废矿车里。 
矿车停在平直的废弃的轨道上。 
他的面前是两个按钮,分别写着“F”和“B”。

小明突然记起来,这两个按钮可以控制矿车在轨道上前进和后退。 
按F,会前进97米。按B会后退127米。 
透过昏暗的灯光,小明看到自己前方1米远正好有个监控探头。 
他必须设法使得矿车正好停在摄像头的下方,才有机会争取同伴的援助。 
或许,通过多次操作F和B可以办到。

矿车上的动力已经不太足,黄色的警示灯在默默闪烁… 
每次进行 F 或 B 操作都会消耗一定的能量。 
小明飞快地计算,至少要多少次操作,才能把矿车准确地停在前方1米远的地方。

请填写为了达成目标,最少需要操作的次数。
思路:当然可以暴力搜,,也可以把 97 -127 1 带入

当然,,,还可以手推,,,,

答案就是97啦

然后,,,,先上一堆概念吧,代码都在下面

//求逆元
        static long inverseelement(long a,long mo)throws Exception{
            long d =  linearequation(a,mo,1);
            x = (x%mo+mo)%mo;   //保证x>0
            return d;
        } //我不知道这个d是啥,,,但是要求的x可能就是代码中的x,,在整个类中,他又被定义,,所以直接拿来就可以了

这个代码和上面的扩展欧几里得在同一个类中

文字说明:ax≡1 (mod p)即ax-yp=1.把y写成+的形式就是ax+py=1,为方便理解下面我们把p写成b就是ax+by=1。
就表示x是a的模b乘法逆元,y是b的模a乘法逆元。然后就可以用扩展欧几里得求了。

知道逆元怎么算之后,那么乘法逆元有什么用呢?

做题时如果结果过大一般都会让你模一个数,确保结果不是很大,而这个数一般是1e9+7,而且这个数又是个素数,
加减乘与模运算的顺序交换不会影响结果,但是除法不行。有的题目要求结果mod一个大质数,如果原本的结果中有除法,比如除以a,那就可以乘以a的逆元替代。
(除一个数等于乘它的倒数,虽然这里的逆元不完全是倒数,但可以这么理解,毕竟乘法逆元就是倒数的扩展)。

例题:http://acm.hdu.edu.cn/showproblem.php?pid=1576

Problem Description

要求(A/B)%9973,但由于A很大,我们只给出n(n=A%9973)(我们给定的A必能被B整除,且gcd(B,9973) = 1)。

Input

数据的第一行是一个T,表示有T组数据。
每组数据有两个数n(0 <= n < 9973)和B(1 <= B <= 10^9)。

Output

对应每组数据输出(A/B)%9973。

Sample Input

2 1000 53

87 123456789

Sample Output

7922 6060

思路:上面有手写,,大致就是(A/B)%9978     就等于B的逆元成A%9973 

求B的逆元的话 直接把B和9973带入求逆元的函数就可以了

模板:

private static class ext{
        static long x;
        static long y;
        //扩展欧欧几里得
        // 调用后 x和y是ax+by=gcd(a,b)的解,这个解是上面求得的x和y再乘m比d的数字
        static long ext_gcd(long a,long b){
            if(b==0){
                x=1;
                y=0;
                return a;
            }
            long res = ext_gcd(b,a%b);
            long x1 = x; //备份x
            x = y;  //4
            y = x1-a/b*y; //更新y
            return res;
        }

        //线性方程 
        //ax+by = m 当m为gcd(a,b)的倍数时,有解

        static long linearequation(long a,long b,long m) throws Exception{
            long d = ext_gcd(a,b);
            //如果m不是d的倍数 ,就无解
            if(m%d !=0){
                throw new Exception(m+"%"+"gcd("+a+","+b+")"+"!=0 ~无解");
            }
            long n = m/d;
            x*= n;
            y*=n;
            return d;
        }

        //求逆元
        static long inverseelement(long a,long mo)throws Exception{
            long d =  linearequation(a,mo,1);
            x = (x%mo+mo)%mo;   //保证x>0
            return d;
        }
    }

此题代码: 

import java.util.Scanner;

class Main{
    private static class ext{
        static long x;
        static long y;
        //扩展欧欧几里得
        // 调用后 x和y是ax+by=gcd(a,b)的解,这个解是上面求得的x和y再乘m比d的数字
        static long ext_gcd(long a,long b){
            if(b==0){
                x=1;
                y=0;
                return a;
            }
            long res = ext_gcd(b,a%b);
            long x1 = x; //备份x
            x = y;  //4
            y = x1-a/b*y; //更新y
            return res;
        }

        //线性方程 
        //ax+by = m 当m为gcd(a,b)的倍数时,有解

        static long linearequation(long a,long b,long m) throws Exception{
            long d = ext_gcd(a,b);
            //如果m不是d的倍数 ,就无解
            if(m%d !=0){
                throw new Exception(m+"%"+"gcd("+a+","+b+")"+"!=0 ~无解");
            }
            long n = m/d;
            x*= n;
            y*=n;
            return d;
        }

        //求逆元
        static long inverseelement(long a,long mo)throws Exception{
            long d =  linearequation(a,mo,1);
            x = (x%mo+mo)%mo;   //保证x>0
            return d;
        }
    }
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int T  = sc.nextInt();
        for(int i=0;i<T;i++){
            int n = sc.nextInt();
            int b= sc.nextInt();
            try{
                ext.inverseelement(b,9973);
                long x = ext.x;
                System.out.println(x*n%9973);
            }catch(Exception e){
                e.printStackTrace();
            }
        }

    }
}

同余方程组:跟上面的模板放到一起好了,,这里我也没有太懂

public static long linearEquationGroup(long[] r, long[] m) throws Exception {
    int len = r.length;
    if (len == 0 && r[0] == 0) return m[0];
    long R = r[0];
    long M = m[0];
    for (int i = 1; i < len; i++) {
      //这里往前看是两个方程
      long c = r[i] - R;
      long d = linearEquation(M, m[i], c);
      //现在的static x是k1,用k1求得一个特解
      long x0 = R + M * x;//特解-》解系:X=x0+k*lcm(m1,m2)->得新方程: X 三 x0 mod lcm
      long lcm = M * m[i] / d;//这是新的m
      M = lcm;
      R = x0 % lcm;//x0变成正数
    }
    //合并完之后,只有一个方程 : X mod M = R
    while (R < 0)
      R += M;
    return R;
  }  

调用的时候这么调用 

public static void main(String[] args) {

        long[] a = {2,3,2};  //余数的数组
        long[] m = {3,5,7};  //模的数组
        try{
            long res =ext.linearEquationGroup(a,m);
            System.out.println(res);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

例题:http://poj.org/problem?id=1006&lang=zh-CN&change=true

d1+23k = x

d2+28k =x

d3+33k =x

即: x===(三条杠)d1(%23)

   x===(三条杠)d2(%28)

   x===(三条杠)d3(%33)

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

class test3 {
    private static class ext {
        static long x;
        static long y;

        //扩展欧欧几里得
        // 调用后 x和y是ax+by=gcd(a,b)的解,这个解是上面求得的x和y再乘m比d的数字
        static long ext_gcd(long a, long b) {
            if (b == 0) {
                x = 1;
                y = 0;
                return a;
            }
            long res = ext_gcd(b, a % b);
            long x1 = x; //备份x
            x = y;  //4
            y = x1 - a / b * y; //更新y
            return res;
        }

        //线性方程 
        //ax+by = m 当m为gcd(a,b)的倍数时,有解

        static long linearequation(long a, long b, long m) throws Exception {
            long d = ext_gcd(a, b);
            //如果m不是d的倍数 ,就无解
            if (m % d != 0) {
                throw new Exception(m + "%" + "gcd(" + a + "," + b + ")" + "!=0 ~无解");
            }
            long n = m / d;
            x *= n;
            y *= n;
            return d;
        }

        //求逆元
        static long inverseelement(long a, long mo) throws Exception {
            long d = linearequation(a, mo, 1);
            x = (x % mo + mo) % mo;   //保证x>0
            return d;
        }

        public static long linearEquationGroup(long[] r, long[] m) throws Exception {
            int len = r.length;
            if (len == 0 && r[0] == 0) return m[0];
            long R = r[0];
            long M = m[0];
            for (int i = 1; i < len; i++) {
                //这里往前看是两个方程
                long c = r[i] - R;
                long d = linearequation(M, m[i], c);
                //现在的static x是k1,用k1求得一个特解
                long x0 = R + M * x;//特解-》解系:X=x0+k*lcm(m1,m2)->得新方程: X 三 x0 mod lcm
                long lcm = M * m[i] / d;//这是新的m
                M = lcm;
                R = x0 % lcm;//x0变成正数
            }
            //合并完之后,只有一个方程 : X mod M = R
            while (R < 0)
                R += M;
            return R;
        }
    }

    public static void main(String[] args) throws Exception {
        Scanner sc = new Scanner(System.in);
        int t = 1;
        List<long[]> alist = new ArrayList<long[]>();
        List<Long> dlist = new ArrayList<Long>();
        while (sc.hasNext()) {
            long[] a = {sc.nextLong(), sc.nextLong(), sc.nextLong()};
            long d = sc.nextLong();
            if (a[0] == -1 && a[1] == -1 && a[2] == -1 && d == -1) {
                break;
            } else {
                alist.add(a);//把一整个a数组都加进去了,(三个数)
                dlist.add(d);
            }
        }
        for (int i = 0; i < alist.size(); i++) {
            long[] a = alist.get(i);
            long d = dlist.get(i);
            long[] m = {23, 28, 33};
            long res = ext.linearEquationGroup(a, m);
            while (res <= d) {
                res += 21252;
            }
            System.out.println("Case" + (t++) + ":the next riple peak occurs in " + (res - d) + "days.");
        }
    }
}

10、质数与质因数分解模板

import java.util.HashMap;
import java.util.Map;

class test3{
    public static void main(String[] args) {
        boolean res = isPrime(100);
        System.out.println(res);
        System.out.println(primeFactor(100));
        StringBuilder sb = new StringBuilder();
        Map<Integer,Integer>map = primeFactor(100);
        for(Map.Entry<Integer,Integer> entry :map.entrySet()){
            int k = entry.getKey();
            int v = entry.getValue();
            for(int i=0;i<v;i++){
                sb.append("*"+k);
            }
        }
        System.out.println(sb.substring(1));
    }
    //测试是否为质数
    static boolean isPrime(long num){
        for(int i=2;i*i<=num;i++){
            if(num%i==0) return false;
        }
        return true;
    }

    //质因数分解
    static Map<Integer,Integer> primeFactor(int num){
        Map<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;
    }
}

11、素数的筛法

判断一段数字中素数的个数

我如果要求第10万个素数 用以前的算法复杂度在10万×log 10万

这个算法是艾氏筛法  时间差10倍

class test3{
    public static void main(String[] args) {
        long now = System.currentTimeMillis();
        m1(100000);
        System.out.println("耗时"+(System.currentTimeMillis()-now)+"ms");
    }

    //求第n个素数
    static void m1(int N){
        int n= 2;//n是我猜测的空间,,一直自己去增大
        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;
            while (x*k<n){
                arr[x*k] = -1;
                k++;
            }
            x++;
        }
        //筛完后,所有非素数的下标都是-1
        int sum =0;
        for(int i=2;i<arr.length;i++){
            if(arr[i]==0){
                sum++;
            }
            if(sum==N){
                System.out.println(i);//一旦sum==N,那他的i就是我们求得数字
                return;
            }
        }
    }


}

12、快速幂运算

class Test48{
    public static void main(String[] args) {

    }
    //之前的
    static int ex(int a,int n){
        if(n==1){
            return a;
        }
        int res = 1;
        int temp = a;
        int exponent =1;
        while ((exponent<<1)<n){
            temp = temp*temp;
            exponent = exponent<<1;
        }
        res*=ex(a,n-exponent); //之前这里是直接用的pow函数,,这次这里直接调用回去也好
        return res*temp;
    }

    //新的办法(二进制办法)
    static long ex2(long n,long m){
        long pingfangshu = n;
        long result = 1;
        while (m!=0){
            //遇到1就累成现在的幂
            if((m&1)==1){//&逐位与运算
                result*=pingfangshu;
            }
            pingfangshu  = pingfangshu*pingfangshu;
           //右移一位
            m>>=1;
        }
        return result;
    }
    
    
}

斐波那契与矩阵幂运算

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值