王道机考系列——数学问题

数学问题

% 运算符

数位拆解

进制转换

  1. 输入两个不超过整形定义的非负10进制整数A和B,输出A+B的m进制数。
    A , B &lt; = 2 23 − 1 A, B &lt;= 2^{23} - 1 A,B<=2231

         输入样例:              样例输出:
             8 1300 48             2504  
             2 1 7                 1000
             0
    
#include <iostream>
#include <string.h>
using namespace std;

void TenToM(long data, int m, int arr[], int &len) {
    //  十进制到m进制的转换
    long long temp = data;
    len = 0;
    while (temp) {
        arr[len] = temp % m;
        temp = temp / m;
        len++;
    }
}

void AddM(int m, int arr1[], int arr2[], int arr[], int l1, int l2, int &l) {
    //  m进制的两个数相加
    int len = l1 < l2 ? l1 : l2;
    l = l1 < l2 ? l2 : l1;
    int index = 0;
    int add = 0;
    for (int i = 0; i < len; i++) {
        add = arr1[i] + arr2[i] + index;
        if (add >= m) {
            index = 1;
            add = add % m;
        } else {
            index = 0;
        }
        arr[i] = add;
    }
    if (l1 < l2) {
        for(int i = len; i < l2; i++) {
            add = arr2[i] + index;
            if (add >= m) {
                index = 1;
                add = add % m;
            } else {
                index = 0;
            }
            arr[i] = add;
        }
    } else if (l1 > l2) {
        for(int i = len; i < l1; i++) {
            add = arr1[i] + index;
            if (add >= m) {
                index = 1;
                add = add % m;
            } else {
                index = 0;
            }
            arr[i] = add;
        }
    }

    if (index == 1) {
        arr[l] = 1;
        l += 1;
    }
}

int main() {
    int m;
    long long A, B;
    while(scanf("%d%lld%lld", &m, &A, &B) == 3) {
        int arrA[32] = {0};
        int arrB[32] = {0};
        int ans[33] = {0};
        int lena = 0;
        int lenb = 0;
        int lenans = 0;
        TenToM(A, m, arrA, lena);
        TenToM(B, m, arrB, lenb);
        AddM(m, arrA, arrB, ans, lena, lenb, lenans);

        for(int i = lenans - 1; i >= 0; i--) {
            printf("%d", ans[i]);
        }
        printf("\n");
    }
    return 0;
}

最大公约数

题目:求两个正整数的最大公约数
输入:两个正整数;
输出:最大公约数。
#include <iostream>
#include <string.h>
using namespace std;

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

int main() {
    int a, b;
    while(scanf("%d%d", &a, &b) != EOF) {
        printf("%d\n", GCD(a, b));
    }
    return 0;
}

最小公倍数

最小公倍数的等于两个数的乘积除以他们的最大公约数。

#include <iostream>
using namespace std;

int GCD(int a, int b) {
    return b != 0 ? GCD(b, a % b) : a;
}

int main() {
    int a, b;
    while(scanf("%d%d", &a, &b) != EOF) {
        printf("%d\n", a * b / GCD(a, b));
    }
    return 0;
}

素数判定

  1. 输入一个数,判断这个数是否是素数。
#include <iostream>
#include <math.h>
using namespace std;

bool IsPrim(int n) {
    //  0 1 负数都是非素数
    if (n < 2) return false;
    int index = (int)sqrt(n) + 1;
    for (int i = 2; i < index; i++)
        if (n % i == 0) return false;
    return true;
}


int main() {
    int a;
    while(scanf("%d", &a) != EOF) {
        if (IsPrim(a))
            printf("yes\n");
        else
            printf("no\n");
    }
    return 0;
}

求解素数

给定一个数,给出这个数范围内的所有素数,并输出,最后一个数后面没有空格。
思路:从2开始,当遍历到一个数的时候将所有这个数的倍数标记为非素数,
所以当我们遍历到某个数的时候如果它没有被比它小的数标记为非素数的话它就是素数。
#include <iostream>
#include <stdio.h>
using namespace std;

int prime[10000];   //  保存找出的素数
int primenum = 0;   //  保存找出的素数的个数
bool mark[10000] = {false};

void init() {
    //  找出所有小于10001的素数
    for (int i = 2; i <= 10000; i++) {
        if (mark[i] == false) {
            prime[primenum++] = i;
            //  从i*i开始标记是因为比i*i小的数据都在之前就有标记过了。
            for (int j = i * i; j <= 10000; j += i)
                mark[j] = true;
        }
    }
}

int main() {
    int a;
    init();
    while(scanf("%d", &a) != EOF) {
        bool isoutput = false;
        for (int i = 0; i < primenum; i++) {
            if (prime[i] <= a) {
                if (isoutput == false) {
                    printf("%d", prime[i]);
                    isoutput = true;
                } else {
                    printf(" %d", prime[i]);
                }
            } else {
                break;
            }
        }
    }
    return 0;
}

分解素因数

求解质因数

给定一个数,求这个这个数的质因数的个数。

输入:
    120(120 = 2 * 2 * 2 * 3 * 5);

输出:
    5
#include <stdio.h>
#include <iostream>
using namespace std;

long prime[100000];
long primenum = 0;
bool mark[100000] = {false};

void init() {
    for (long i = 2; i < 100000; i++) {
        if (mark[i] == false) {
            prime[primenum++] = i;
            if (i >= 1000) continue;
            for (long j = i * i; j < 100000; j += i)
                mark[j] = true;
        }
    }
}

int main() {
    int a;
    init();
    while(scanf("%d", &a) != EOF) {
        int n = a;
        int num = 0;
        for (int i = 0; i < primenum; i++) {
            if (n % prime[i] == 0) {
                num++;
                n = n / prime[i];
                while(n % prime[i] == 0) {
                    num++;
                    n = n / prime[i];
                }
           }
           if (n == 1) break;
       }
       if (n != 1) {
           num++;
       }
       printf("%d\n", num);
    }
    return 0;
}

整除问题

给定n, a求最大的k,使得n!可以被a^k整除但不能被a^(k+1)整除。

输入:两个整数n, a.(2 <= n <= 1000, 2 <= a <= 1000).

输出:一个整数。

样例输入:6 10

样例输出:1

分析: 由于n!和 a^k数字会变得很大,甚至long long数据类型都无法保存,所以不能暴力计算出值之后再求k,其中一个可以采用的方法就是质因数分解,将n!和ak都进行质因数分解,再对分解的结果进行计算就好,n!和ak的质因数分解结果如下所示:
b = p 1 e 1 p 2 e 2 p 3 e 3 . . . . p n e n b = p1^{e1} p2^{e2} p3^{e3}....pn^{en} b=p1e1p2e2p3e3....pnen
a = p 1 ′ e 1 ′ p 2 ′ e 2 ′ p 3 ′ e 3 ′ . . . . p n ′ e n ′ a = p1&#x27;^{e1&#x27;} p2&#x27;^{e2&#x27;} p3&#x27;^{e3&#x27;}....pn&#x27;^{en&#x27;} a=p1e1p2e2p3e3....pnen
a k = p 1 ′ k e 1 ′ p 2 ′ k e 2 ′ p 3 ′ k e 3 ′ . . . . p n ′ k e n ′ a^{k} = p1&#x27;^{ke1&#x27;} p2&#x27;^{ke2&#x27;} p3&#x27;^{ke3&#x27;}....pn&#x27;^{ken&#x27;} ak=p1ke1p2ke2p3ke3....pnken
要想a能被b整除,那么a中存在的质因数p在b中也必然存在,并且a中的幂指数必定不大于b中的p的幂指数。

现在令x = n!, y = a^k,则
x = p 1 e 1 p 2 e 2 p 3 e 3 . . . . p n e n x = p1^{e1} p2^{e2} p3^{e3}....pn^{en} x=p1e1p2e2p3e3....pnen
y = p 1 ′ k e 1 ′ p 2 ′ k e 2 ′ p 3 ′ k e 3 ′ . . . . p n ′ k e n ′ y = p1&#x27;^{ke1&#x27;} p2&#x27;^{ke2&#x27;} p3&#x27;^{ke3&#x27;}....pn&#x27;^{ken&#x27;} y=p1ke1p2ke2p3ke3....pnken
现在要确定k使得y中的任一素因数的幂指数的k倍依旧小于或等于x中的幂指数,要求得k只需要一次测试a中的每一个素因数就可以了。

所以,现在问题转化为求n!和a^k 的素因数的问题了,a^k的素因数比较容易,求出a的素因数就很容易了,关键是n!的素因数,n!是从1到n的数字的乘积,这些乘积中,每一个素因数p的倍数都将为n!至少贡献一个素因子,则p至少提供n/p个素因子,每一个p * p都将为n!至少贡献两个素因子,则至少为n!贡献n / (p * p)个素因子,p * p * p同理。

下面以10!为例来简单展示以上讨论:
10 ! = 10 ∗ 9 ∗ 8 ∗ 7 ∗ 6 ∗ 5 ∗ 4 ∗ 3 ∗ 2 10! = 10 * 9 * 8 * 7 * 6 * 5 * 4 * 3 * 2 10!=1098765432
10以内的素因数有:2 3 5 7这四个。

2 为 10! 提供 10 / 2 = 5 个素因数, 2 * 2 为10!提供 10 / (2 * 2) = 2,个素因数,所以2提供5 + 2个素因数。这7个素因数分别来自 2、4、6、8、10,其中2里1个,4里2个,6里1个,8里3个,10里1个。

同理可对3、5、7进行计算。

这样就将原本计算n!的素因数的数学计算转化为了计算1 ~ n的素因数的问题了。

#include <stdio.h>
#include <math.h>
#define NUM 1001

int prime[NUM] = {0};     //  存储2-NUM的素因数。
int primenum = 0;         //  记录NUM以内的所有素数的个数
int primen[NUM] = {0};    //  存储n的所有素因数
int primea[NUM] = {0};    //  存储a的所有素因数
int numn[NUM] = {0};      //  存储n!的每一个素因数的个数
int numa[NUM] = {0};      //  存储a的每一个素因数的个数
bool mark[NUM] = {false};

void init() {
// 将NUM以内的所有素因数存储起来。
    int index = (int)sqrt(NUM) + 1;
    for (int i = 2; i < NUM; i++) {
        if (mark[i] == false) {
            prime[primenum++] = i;     //  记录下素数
            if (i > index) continue;
            for (int j = i * i; j < NUM; j += i)   //将所有i的倍数标记为非素数
                mark[j] = true;
        }
    }
}

void Primen(int n, int &len) {
    //  计算n!的素数
    for (int i = 0; i < NUM; i++) {
        primen[i] = 0;
        numn[i] = 0;
    }
    int p = 0;
    int index = 0;
    int temp = n;
    for (int i = 0; i < primenum; i++) {
        p = prime[i];
        temp = n;
        if (temp / p > 0) {
            while(temp) {
                primen[index] = p;  //  记录下素数
                numn[index] += temp / p;
                temp = temp / p;
            }
            index++;
        }
    }
    len = index;
}

void Primea(int a, int &len) {
    for (int i = 0; i < NUM; i++) {
        //  先将计数器清零
        numa[i] = 0;
        primea[i] = 0;
    }
    int temp = a;
    int p = 0;
    int index = 0;
    for (int i = 0; i < NUM; i++) {
        p = prime[i];
        if (temp % p == 0) {
            while(temp % p == 0) {
                primea[index] = p;
                numa[index]++;
                temp = temp / p;
            }
            index++;
        }
        if (temp == 1) break;
    }
    len = index;
}

int Findk(int ln, int la) {
    int temp = 0, index = 0;
    for (int k = 0; ;k++) {
        for (int i = 0; i < la; i++) {
                //  确保a的质因数都在n!的质因数中,如果不在,则返回0
            temp = numa[i] * k;
            for (index = 0; index < ln, primea[i] != primen[index]; index++);
            if (index >= ln) return 0;
            if (numn[index] < temp) return k - 1;
        }
    }
}

int main() {
    int n;
    int a;
    init();  // 先初始化,找出1001以内的所有素数
    while(scanf("%d%d", &n, &a) != EOF) {
        
        int lenn = 0;
        int lena = 0;
        int k = 0;
        int temp = 0;
        Primen(n, lenn);  //  计算n!的素因数分解
        Primea(a, lena);  //  计算a的素因数分解
        printf("%d\n", Findk(lenn, lena));
    }
    return 0;
}

二分求幂

题目描述:求A^B的最后三位数表示的整数(A^B表示A的B次方)。
A 与 B均为0,即结束。
样例输入:              样例输出:
2 3                     8
12 6                    984
6789 10000              1
0 0 
#include <stdio.h>

int main() {
    int a, b;
    while(scanf("%d%d", &a, &b) != EOF) {
        if (a == 0 && b == 0) break;
        int ans = 1;
        while(b) {
            if (b % 2 == 1) {
                ans *= a;
                ans = ans % 1000;
                //  因为题目只要求保留后三位数字
            }
            b = b / 2;
            a *= a;
            a = a % 1000;
        }

        printf("%d\n", ans);
    }
    return 0;
}

上面介绍的二分求幂的方法其实就是把幂指数进行二分分解使用二进制来表示幂指数,例如:
1 2 6 = 1 2 2 2 ∗ 1 2 2 1 , 6 的 二 进 制 表 示 是 1010 只 需 要 在 两 个 1 的 位 置 完 成 计 算 即 可 12^{6} = 12^{2^{2}}*12^{2^{1}}, 6的二进制表示是1010 只需要在两个1的位置完成计算即可 126=12221221,610101

以12^6为例,解释循环中的操作:
循环次数    ans的值             b的值   a的值  
  0         1                   6       12
  1         1                   3       12^2
  2         1*12^2              1       12^2 * 12^2
  3         1*12^2*12^2*12^2    0       (12^2*12^2)^2
如果以正常的循环来求的话要求6次,6个循环才能求出12^6的值,但是使用二分求幂,
只需要4次循环,当幂指数变得很大的时候这种做法可以节约一半的时间。

高精度整数

高精度整数数值非常巨大,无法使用计算机的内置数据类型来保存它的值,所以只能使用结构体来存储,高精度整数的内置类型如下所示:

    struct BigInteger {
        int digit[1000];    
        int size;
        //  每四位使用一个存储单元,例如整数123456789,存出结果为
        //  digit[0] = 6789, digit[1] = 2345, digit[2] = 1;
        //  size = 3;
    };

高精度加法

题目: 实现一个加法器,使其能够输出a+b的值
输入:两个数a和b,每个数不超过1000位
输出:可能有多组测试数据,对每组数据输出a+b的值
样例输入:
2 6
10000000000000000000 1000000000000000000000000000000000
样例输出:
8
1000000000000010000000000000000000
#include <stdio.h>
#include <string.h>

struct BigInteger {
    int digit[1000];
    int size;
    void Init() {
        size = 0;
        for (int i = 0; i < size; i++)
            digit[i] = 0;
    }

    void Set(char str[]) {
        //  将输入的字符串转化为整数存储在digit中
        Init();
        int len = strlen(str);
        int index = 0;        //  计数,每四位数记录一次
        int num = 0;          //  记录转换过程中的数字
        int tenq = 1;         //  权重
        for (int i = len - 1; i >= 0; i--) {
            num += (str[i] - '0') * tenq;
            tenq *= 10;
            index++;
            if (index == 4 || i == 0) {
                digit[size++] = num;
                num = 0;
                tenq = 1;
                index = 0;
            }
        }
    }

    void Ouput() {
        //  将运算后的结果用字符串输出出来。
        for (int i = size - 1; i >= 0; i--) {
            if (i != size - 1) printf("%04d ", digit[i]);
            else printf("%d ", digit[i]);
            //  如果不是最高位,那么不足4位时,用0补足,例如110001在digit中的存储为
            //  digit[0] = 1, digit[1] = 11, 由于0001在实际存储时会变为1,
            //  所以需要补足0;

        }
    }

    BigInteger operator + (const BigInteger &A) const {
        //  加法运算符;
        BigInteger ret;
        ret.Init();
        int carry = 0;
        int add = 0;
        for (int i = 0; i < size || i < A.size; i++) {
            //  完成相同长度下的加法运算
            add = digit[i] + A.digit[i] + carry;
            carry = add / 10000;
            ret.digit[i] = add % 10000;
            ret.size++;
        }
        if (carry) {
            //  有进位则进1
            ret.digit[size] = carry;
            ret.size++;
        }
        return ret;
    }
};

int main() {
    char A[1000], B[1000];
    while(scanf("%s %s", A, B) != EOF) {
        BigInteger BigA, BigB, BigC;
        BigA.Set(A);
        BigB.Set(B);
        BigC = BigA + BigB;
        BigC.Ouput();
        printf("\n");
    }
    return 0;
}

N的阶乘

题目描述:输入一个数N,输出N的阶乘。
输入:正整数N(0 <= N <= 1000)
输出:有可能有多组输入,对于每一组输入,输出N!
样例输入:
4
5
15
样例输出:
24
120
1307674368000
#include <stdio.h>
#include <string.h>

struct BigInteger {
    int digit[1000];
    int size;
    void Init() {
        size = 0;
        for (int i = 0; i < size; i++)
            digit[i] = 0;
    }

    void Set(int x) {
        //  将输入的字符串转化为整数存储在digit中
        Init();
        do {
            digit[size++] = x % 10000;
            x = x / 10000;
        } while(x);
        //  使用 do-while循环是要确保x等于0时也能被处理
    }

    void Ouput() {
        //  将运算后的结果用字符串输出出来。
        for (int i = size - 1; i >= 0; i--) {
            if (i != size - 1) printf("%04d", digit[i]);
            else printf("%d", digit[i]);
            //  如果不是最高位,那么不足4位时,用0补足,例如110001在digit中的存储为
            //  digit[0] = 1, digit[1] = 11, 由于0001在实际存储时会变为1,
            //  所以需要补足0;

        }
    }

    BigInteger operator * (const int x) const {
        //  乘法运算符;
        BigInteger ret;
        ret.Init();
        int carry = 0;
        int pro = 0;
        for (int i = 0; i < size; i++) {
            pro = digit[i] * x + carry;
            ret.digit[i] = pro % 10000;
            carry = pro / 10000;
        }
        ret.size = size;
        //  进位这里需要注意,曾经因为将进位设置为1而出错,进位不一定为1
        if(carry) ret.digit[ret.size++] = carry;
        return ret;
    }
};

int main() {
    int n;
    while(scanf("%d", &n) != EOF) {
        BigInteger A;
        A.Init();
        A.Set(n);
        for (int i = 2; i < n; i++)
            A = A * i;
        A.Ouput();
        printf("\n");
    }
    return 0;
}

进制转换

题目描述:将M进制数转换为N进制数。
输入:输入的第一行包括两个整数:M和N (2 <= M, N <= 30);
     输入的下一行是一个数X,数是M进制数,将其转化为N进制数,注意X的输入是一个字符串;
输出:
    输出X的N进制数。
样例输入:
    16 10
    F
样例输出:
    15
提示:输入时字母部分为大写,输出时转化为小写,可能有大数据。

思路: 将M进制数转化为10进制数,再将10进制数转化为N进制数。

#include <stdio.h>
#include <string.h>
#define MAXSIZE 100

struct BigInteger {
    int digit[1000];
    int size;
    void Init() {
        size = 0;
        for (int i = 0; i < size; i++)
            digit[i] = 0;
    }

    void Set(int x) {
        //  将输入的字符串转化为整数存储在digit中
        Init();
        do {
            digit[size++] = x % 10000;
            x = x / 10000;
        } while(x);
        //  使用 do-while循环是要确保x等于0时也能被处理
    }

    void Ouput() {
        //  将运算后的结果用字符串输出出来。
        for (int i = size - 1; i >= 0; i--) {
            if (i != size - 1) printf("%04d", digit[i]);
            else printf("%d", digit[i]);
            //  如果不是最高位,那么不足4位时,用0补足,例如110001在digit中的存储为
            //  digit[0] = 1, digit[1] = 11, 由于0001在实际存储时会变为1,
            //  所以需要补足0;

        }
    }

    BigInteger operator + (const BigInteger &A) const {
        //  加法运算符;
        BigInteger ret;
        ret.Init();
        int carry = 0;
        int add = 0;
        for (int i = 0; i < size || i < A.size; i++) {
            //  完成相同长度下的加法运算
            add = digit[i] + A.digit[i] + carry;
            carry = add / 10000;
            ret.digit[i] = add % 10000;
            ret.size++;
        }
        if (carry) {
            //  有进位则进1
            ret.digit[size] = carry;
            ret.size++;
        }
        return ret;
    }

    BigInteger operator * (const int x) const {
        //  加法运算符;
        BigInteger ret;
        ret.Init();
        int carry = 0;
        int pro = 0;
        for (int i = 0; i < size; i++) {
            pro = digit[i] * x + carry;
            ret.digit[i] = pro % 10000;
            carry = pro / 10000;
        }
        ret.size = size;
        if(carry) ret.digit[ret.size++] = carry;
        return ret;
    }

    BigInteger operator / (const int x) const {
        BigInteger ret;
        ret.Init();
        int divide = 0;     //  保存除数
        int reminder = 0;   //  保存余数
        for (int i = size - 1; i >= 0; i--) {
            divide = (reminder * 10000 + digit[i]) / x;
            reminder = (reminder * 10000 + digit[i]) % x;
            ret.digit[i] = divide;
        }

        for (int i = 0; i < MAXSIZE; i++)
            if(ret.digit[i] != 0) ret.size = i;
        ret.size++;
        return ret;
    }

    int operator % (int x) const {
        int reminder = 0;
        int divide = 0;
        for (int i = size - 1; i >= 0; i--) {
            divide = (reminder * 10000 + digit[i]) / x;
            reminder = (reminder * 10000 + digit[i]) % x;
        }
        return reminder;
    }
};

int main() {
    int m, n;
    char str[1000];    //  输入的需要转化的数字
    char ans[1000];    //  转化后的n进制数

    while(scanf("%d %d", &m, &n) != EOF) {
        BigInteger BigA;   //  存储m转化为10进制后的数字
        BigInteger BigB;   //  存储计算过程中的权重信息
        BigA.Set(0);
        BigB.Set(1);

        scanf("%s", str);
        int len = strlen(str);
        int digit;
        for(int i = len - 1; i >= 0; i--) {
            if(str[i] >= '0' && str[i] <= '9') {
                digit = str[i] - '0';
            } else {
                digit = str[i] - 'A' + 10;
            }
            BigA = BigA + BigB * digit;
            BigB = BigB * m;
        }

        int index = 0;
        int divide = 0;
        int reminder = 0;
        do {
            reminder = BigA % n;
            BigA = BigA / 10;
            if (reminder >= 10) ans[index++] = reminder - 10 + 'a';
            else ans[index++] = reminder + '0';

        } while(BigA.digit[0] != 0 || BigA.size != 1);

        for (int i = index - 1; i >= 0; i--)
            printf("%c", ans[i]);
        printf("\n");
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值