chapter6-数学问题

在机试中,有这样一类问题,它们并不涉及很深奥的算法和数据结构,而只与数理逻辑相关,这类问题称为数学问题。解决这类问题不需要用到特别高深的算法与数据结构,而只需要掌握简单的数理逻辑知识。

1.进制转换

包括三类常见的转换。

1.1 10进制转为N进制

1.2 M进制转为10进制

1.3 M进制转为N进制

2.GCD&LCM

2.1 最大公约数

采用辗转相除法,假设有a和b两个整数,a % b = remain,经过数学验证可知,a与b的GCD,同时必定也是b与remain的GCD,这样不断缩小数据的规模,直到是一个非零数和零做除法,则这个非零数即为所求的Greatest Common Divisor。

可以用递归非常简洁的实现,也可以写一个循环解决。

2.2 最小公倍数

a,b两个数的最小公倍数为两数的乘积除以它们的最大公约数。

3.质数

3.1 素数判定

质数也称素数,是指只能被其自身和1整除的正整数。判断一个数是否为素数,可以用所有小于该数的正整数去试着整除该数,若存在某个数能够整除该数,则该数不是素数;若这些数都不能整除它,则该数为素数。这一朴素的算法思想的时间复杂度为O(n)。

但,实际上并不用测试到n-1,而只需测试到sqrt(n)(即根号n)的整数,若到这个整数为止,所有正整数均不能整除n,则可以断定n为素数。测试一个数是否是素数的复杂度就O(n)降至了O(sqrt(n))。

sqrt(n)是众所周知的几个比较耗时的函数之一,所以降低时间复杂度的小技巧是在for循环之前,先把sqrt(n)的值赋给变量bound

3.2 素数筛选法

在学会了如何判断一个数是否为素数之后,那么该如何找出0与1e6之间的所有素数呢?

依次枚举过于暴力,求解该问题更优雅的方法是:找到一个素数,将它的所有倍数均标记成非素数;这样,当遍历到一个数时,若它未被任何小于它的素数标记为非素数,则确定其为素数。

素数筛选法
//素数筛选法 2024-02-28
#include <iostream>
#include <cstdio>
#include <vector>

using namespace std;

const int MAXN = 1e5 + 10;
bool isPrime[MAXN];
vector<int> Prime;

void Initial() {
    fill(isPrime, isPrime + MAXN, true);
    isPrime[0] = false;
    isPrime[1] = false;
    for(int i = 2; i < MAXN; ++i) {
        if(isPrime[i]) {
            Prime.push_back(i);
        }
        //i*i 过大会超过int范围
        if(i > MAXN / i) {  //i*i > MAXN的等价表示
            continue;
        }
        for(int j = i*i; j < MAXN; j += i) {
            isPrime[j] = false; //筛除
        } 
    }
}

int main()
{
    Initial();
    int k;
    while(cin >> k) {
        cout << Prime[k-1] << endl;
    }
    return 0;
}

上述素数筛选代码中使用了一个技巧,当判定i为素数,要标记其倍数为非素数时,并未从2*i开始标记,而是直接从i*i开始标记的。原因是i*k(k < i)必定已经在求得k的某个素因数时被标记过。

3.3 质因子分解

素数常用于分解质因数。每个数都可以写成一个或几个质数相乘的形式,其中每个质数都是这个数的质因数。把一个数用质因数相乘的形式表示出来,就称为分解质因数。

方法:首先利用素数筛选法,预先筛选出所有可能在题面给定数据范围内是素因数的素数,然后在程序输入待处理数字n时,依次遍历所有小于n的素数,判断其是否为n的因数。若确定某素数为n的因数,则通过试除确定其对应的幂指数。(短除法,从小到大不断试除质数)

质因子的个数
//质因子个数-清华大学上机题 2024-02-29
#include <iostream>
#include <cstdio>
#include <vector>

using namespace std;

const int MAXN = 4e4; //因为两个大于sqrt(1e9)的质因子相乘结果会大于n

vector<int> Prime;
bool isPrime[MAXN];

void Initial()
{
    fill(isPrime, isPrime + MAXN, true);
    isPrime[0] = false;
    isPrime[1] = true;
    for(int i = 2; i < MAXN; ++i) {
        if(isPrime[i]) {
            Prime.push_back(i);
        }
        if(i > MAXN / i) {
            continue;
        }
        for(int j = i * i; j < MAXN; j += i) {
            isPrime[j] = false;
        }
    }
}


int NumberOfPrimeFactors(int number) {
    int answer = 0;
    for(int i = 0; i < Prime.size(); ++i) {
        int factor = Prime[i];
        if(number < factor) {
            break;
        }
        int exponent = 0;
        while(number % factor == 0) {
            number /= factor;
            exponent++;
        }
        answer += exponent;
    }
    if(number > 1) {
        answer += 1;
    }
    return answer;
}

int main()
{
    Initial();
    int n;
    while(cin >> n) {
        int ans = NumberOfPrimeFactors(n);
        cout << ans << endl;
    }
    return 0;
}

4.快速幂

任何一个数字n都可以分解为若干2k之和,如\(23 = 16 + 4 + 2 + 1\)。因此可以先将指数b分解为若干2k的和。

以求329为例,其指数为29,而\(29 = 1 + 4 + 8 + 16\)。分解的数字便是需要的3的次幂,即31、34、38、316,再将它们累乘可得结果。而指数b分解为若干2k,就是在求b的二进制数。

快速幂
//快速幂 2024-02-29
#include <iostream>
#include <cstdio>

using namespace std;

int QuickPower(int x, int n) {
    int answer = 1;
    while(n > 0) {
        if(n % 2 == 1) {
            answer *= x;
        }
        n = n >> 1; //往右移一个单位
        x *= x;
    }
    return answer;
}


int main()
{
    cout << QuickPower(2, 11) << endl;
    return 0;
}

4.2 OJ-2035-人见人爱AB

求AB的结果的最后三位数,其实只与A的后三位数和B有关,(例如xn-1*x的后三位只与xn-1的后三位有关)。因此在计算该结果的过程中产生的中间值也只需保存其后三位数即可。(不断地将中间结果对1000取模)

人见人爱
//人见人爱A^B 2024-02-29
#include <iostream>
#include <cstdio>

using namespace std;

int QuickPower(int x, int n) {
    int answer = 1;
    int mod = 1000;
    while(n > 0) {
        if(n % 2 == 1) {
            answer *= x;
            answer %= mod; //中间结果只保留后三位
        }
        n = n >> 1; //往右移一个单位
        x *= x;
        x %= mod;
    }
    return answer;
}


int main()
{
    int a, b;
    while(cin >> a >> b) {
        if(a == 0 && b == 0) {
            break;
        }
        cout << QuickPower(a, b) << endl;
    }
    return 0;
}

5.矩阵&矩阵快速幂

5.1矩阵

数学问题中另外一个常考的知识点是矩阵。考查的内容不难,一般只考查矩阵的基本运算,如矩阵相加,矩阵相乘,矩阵转置,矩阵求幂,按照加法、乘法的规则进行模拟就可以求解。可用一个二维数组来模拟矩阵。

矩阵基本运算
//矩阵相加、矩阵相乘、矩阵转置、矩阵快速幂
#include <iostream>
#include <cstdio>

using namespace std;

const int MAXN = 100;

typedef struct Matrix {
    int row, col;
    int matrix[MAXN][MAXN];
    Matrix() {}
    Matrix(int r, int c) : row(r), col(c){} //直接赋值
}Matrix;

Matrix Add(Matrix x, Matrix y) {
    Matrix answer = Matrix(x.row, x.col);
    for(int i = 0; i < answer.row; ++i) {
        for(int j = 0; j < answer.col; ++j) {
            answer.matrix[i][j] = x.matrix[i][j] + y.matrix[i][j];
        }
    }
    return answer;
}

Matrix Mutiply(Matrix x, Matrix y) {
    Matrix answer = Matrix(x.row, y.col);
    for(int i = 0; i < answer.row; ++i) {
        for(int j = 0; j < answer.col; ++j) {
            answer.matrix[i][j] = 0;
            for(int k = 0; k < x.col; ++k) {
                answer.matrix[i][j] += x.matrix[i][k] * y.matrix[k][j];
            }
        }
    }
    return answer;
}

Matrix Transpose(Matrix x) {
    Matrix answer = Matrix(x.col, x.row);
    for(int i = 0; i < answer.row; ++i) {
        for(int j = 0; j < answer.col; ++j) {
            answer.matrix[i][j] = x.matrix[j][i];
        }
    }
    return answer;
}
//计算矩阵x的n次幂
Matrix QuickPower(Matrix x, int n) {
    Matrix answer = Matrix(x.row, x.col); //合法输入是n*n矩阵
    //要先初始化为单位矩阵
    for(int i = 0; i < answer.row; ++i) {
        for(int j= 0; j < answer.col; ++j) {
            if(i == j) {
                answer.matrix[i][j] = 1;
            } else {
                answer.matrix[i][j] = 0;
            }
        }
    }
    while(n > 0) {
        if(n % 2 == 1) {
            answer = Mutiply(answer, x);
            n /= 2;
        }
        x = Mutiply(x, x);
    }
    return answer;
}

void InputMatrix(Matrix &x) {
    for(int i = 0; i < x.row; ++i) {
        for(int j = 0; j < x.col; ++j) {
            scanf("%d", &x.matrix[i][j]);
        }
    }
    return ;
}

void OutputMatrix(Matrix x) {
    for(int i = 0; i < x.row; ++i) {
        for(int j = 0; j < x.col; ++j) {
            printf("%d ", x.matrix[i][j]);
        }
        printf("\n");
    }
    return ;
}

int main()
{
    //用构造函数定义两个矩阵并初始化
    Matrix x = Matrix(2, 3);
    Matrix y = Matrix(3, 2);
    InputMatrix(x);
    InputMatrix(y);
    Matrix answer = Mutiply(x, y);
    OutputMatrix(answer);
    return 0;
}

类似于普通快速幂,矩阵的快速幂就是高效求矩阵多幂次的方法,其思想和普通快速幂的思想相同。唯一不同的地方在于,对数字的快速幂而言,其初始值是1;而对于矩阵快速幂而言,其初始值是单位矩阵

6.高精度整数

对高精度整数,要么我们通过各种技巧进行回避。另一种方法,Java内定义了BigInteger类;Python对整数的大小没有限制,不存在这个问题;C/C++则只能自定义一个结构体,并重载构造函数、操作符函数等。

posted @ 2024-03-04 11:32  paopaotangzu  阅读(33)  评论(0)    收藏  举报