数学问题的解题窍门

2018-11-12 20:11:11

数学,特别是数论和计算机科学有着密切的联系,所以也常被选做题材。虽然数学问题大多需要使用特定方法求解,但其中有几个基础算法扮演着重要的角色。

一、辗转相除法

1、求最大公约数

让我们来看一下如下的问题。

问题描述:

给定平面上的两个格点P1(x1, y1)和P2(x2,y2),线段P1P2上,除P1和P2以外一共有几个格点?

限制条件:

-10 ^ 9 <= x1, x2,y1, y2 <= 10 ^ 9

问题求解:

其实本题是两个距离的最大公约数 - 1,要注意特判距离为0时的答案是0。

gcd(a, b) = gcd(b, a % b),直到第二项为0,直接输出第一项。

算法时间复杂度为O(log max(a, b))以内。

int gcd(int a, int b) {
    if (b == 0) return a;
    return gcd(b, a % b);
}

 

2、扩展欧几里得算法

扩展欧几里得定理:对于与不完全为0的非负整数 a, b, gcd(a, b)表示a, b的最大公约数。那么存在整数x,y使得 gcd(a, b)=ax + by。

那么如何高效的求解这个公式呢?

我们不妨设 a>b。
1,显然当 b=0,gcd(a,b)=a。此时 x=1,y=0;
2,ab != 0 时
设 ax 1 +by 1 =gcd(a,b);
bx 2 +(a%b) y 2 =gcd(b,a%b);
根据朴素欧几里德原理有 gcd(a,b)=gcd(b,a%b);
则:ax 1 +by 1 =bx 2 +(a%b)y 2 ;
即:ax 1 +by 1 =bx 2 +(a-(a/b)*b)y 2 =ay 2 +bx 2 -(a/b)*by 2 ;
根据恒等定理得:x 1 =y 2 ; y 1 =x 2 -(a/b)*y 2 ;
这样我们就得到了求解 x1,y1 的方法:x1 ,y1  的值基于 x2 ,y2.

通过上述的变换,就可以将原本求x,y的问题,转化成求x2,y2的问题,并且两个系数在衰减,直到b = 0,答案是x = 1, y = 0。

public class Extgcd {
    public int extgcd(int a, int b, int[] x, int[] y) {
        if (b == 0) {
            int res = a;
            x[0] = 1;
            y[0] = 0;
            return res;
        }
        else {
            int res = extgcd(b, a % b, x, y);
            int tmp = x[0];
            x[0] = y[0];
            y[0] = tmp - a / b * y[0];
            return res;
        }
    }

    public static void main(String[] args) {
        Extgcd e = new Extgcd();
        int[] x = new int[1];
        int[] y = new int[1];
        System.out.println(e.extgcd(4, 11, x, y));
        System.out.println(x[0]);
        System.out.println(y[0]);
    }
}

 

二、有关素数的基础算法

素数广泛应用于密码学中,因而也有很多相关算法。不过程序设计竞赛涉及的主要是埃式筛法、简单的素性测试和整数分解这类算法。

1、素性测试

问题描述:

给定正整数n,请判断n是不是正素数。

限制条件:

1 <= n <= 10 ^ 9

问题求解:

所谓素数,就是指恰好有2个约数的整数。因为n的约数都不超过n,因此只需要判断2 - n - 1即可。另外,如果d是n的约数,那么n / d自然也是其约数,所以只需要检测2 - sqrt(n)。

    public boolean isPrime(int num) {
        for (int i = 2; i * i <= num; i++) {
            if (num % i == 0) return false;
        }
        return num != 1;
    }

 

2、埃式筛法

如果只是对一个数进行素性检测,通常O(sqrt(n))的算法已经够用了。但是如果需要对许多整数进行素性检测,则有更为高效的算法。

问题描述:

给定整数n,请问n以内有多少个素数。

限制条件:

n <= 10 ^ 6

问题求解:

    public int sieve(int n) {
        int res = 0;
        boolean[] isPrime = new boolean[n + 1];
        Arrays.fill(isPrime, true);
        isPrime[0] = isPrime[1] = false;
        for (int i = 2; i <= n; i++) {
            if (isPrime[i]) {
                res++;
                for (int j = 2 * i; j <= n; j += i) {
                    isPrime[j] = false;
                }
            }
        }
        return res;
    }

 

3、区间筛法

问题描述:

给定整数a 和 b,请问区间[a, b)内有多少素数?

限制条件:

a < b <=  10 ^ 12

b - a <= 10 ^ 6

问题求解:

b以内的合数的最小质因数一定是不超过sqrt(b)的,因此如果有了sqrt(b)的素数表,就可以将[a, b)中的素数完全筛选出来。

以下使用POJ #2689测试程序。

需要特别注意一下边界点,如果a = 1,则直接让其自增即可,另外对于长度为1的单个数据点,直接输出即可。

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

public class SegmentSieve {
    public String segmentSieve(long a, long b) {
        if (a + 1 == b) return "There are no adjacent primes.";
        boolean[] small = new boolean[(int)Math.sqrt(b)];
        boolean[] isPrime = new boolean[(int)(b - a)];
        Arrays.fill(small, true);
        Arrays.fill(isPrime, true);
        for (int i = 2; i < small.length; i++) {
            if (small[i]) {
                for (int j = i * 2; j < small.length; j += i) small[j] = false;
                for (long j = Math.max((long)2, (a % i == 0 ? a / i : (a / i) + 1)) * i; j < b; j += i) isPrime[(int)(j - a)] = false;
            }
        }
        int minLen = isPrime.length;
        int maxLen = 0;
        int a1 = -1;
        int b1 = -1;
        int a2 = -1;
        int b2 = -1;
        int prev = -1;
        for (int i = 0; i < isPrime.length; i++) {
            if (isPrime[i]) {
                if (prev == -1) prev = i;
                else {
                    int len = i - prev;
                    if (len < minLen) {
                        minLen = len;
                        a1 = prev;
                        b1 = i;
                    }
                    if (len > maxLen) {
                        maxLen = len;
                        a2 = prev;
                        b2 = i;
                    }
                }
                prev = i;
            }
        }
        if (minLen != isPrime.length) {
            return String.format("%d,%d are closest, %d,%d are most distant.", (long)(a1 + a), (long)(b1 + a), (long)(a2 + a), (long)(b2 + a));
        }
        else return "There are no adjacent primes.";
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        while (sc.hasNext()) {
            long a = sc.nextLong();
            long b = sc.nextLong() + 1;
            if ((int) a == 1) a++;
            SegmentSieve ss = new SegmentSieve();
            System.out.println(ss.segmentSieve(a, b));
        }
    }
}

 

三、快速幂运算

除了数学问题之外,也有很多地方用到了幂运算。可以使用反复平方法来进行快速幂运算,时间复杂度为O(logn)

问题描述:

问题求解:

主要的问题就是在n = Integer.MIN_VALUE的时候,-n会溢出,所以最好把n转成long。

    public double myPow(double x, int n) {
        return pow(x, (long)n);
    }
    
    private double pow(double x, long n) {
        if (n == 0) return 1;
        if (n < 0) {
            n = -n;
            x = 1 / x;
        }
        return (n % 2 == 0) ? pow(x * x, n / 2) : x * pow(x * x, n / 2);
    }

 

posted @ 2018-11-13 23:50  hyserendipity  阅读(285)  评论(0编辑  收藏  举报