c++_数学问题

二进制数

题目:3530. 二进制数 - AcWing题库

输入:包含多组测试数据。

每组数据占一行,包含一个整数( unsigned int 类型)

输出:每组数据输出一行,一个二进制串表示结果。(去掉前导 0)

int main(){
unsigned int num;
    while(scanf("%u",&num)!=EOF){
    //使用一个数组来存储二进制的每一位
    vector<int> arr;
    if(u==0){
        printf("0\n");
        continue;
    }
    while(u>0){
        arr.push_back(u%2);
        u=u/2;
    }
    for(int i=arr.size()-1;i>=0;i--){
        printf("%d",arr[i]);
    }
    printf("\n");
    }
    return 0;
}

代码分析:

1.因为输出只是要打印输出二进制数,所以我们不用真的算出对应的二进制数;而用一个数组来存储它的每一位即可。

因为我们使用取余得到的位数在u对应的二进制中的顺序为(k0,k1,k2,k3),所以我们日后要打印出二进制(k3k2k1k0),应从后往前遍历数组;

2.通过这道题可以学到无符号整数unsigned int 的输入格式是“%u"

3.因为while循环的条件是u>0,但有一种情况是u===0,所以我们要单独处理这种情况。

质数

知识点

1、质数的另一个名字叫素数;

我们可以根据素数的定义,让num去除以从 2 到小于这个数 num 的每个数,看是否能除尽。若其中有能处得尽的,就不是素数。

但其实我们没有必要判断这么多个数,只用判断到 sqrt(x)就停止了。因为

如果比 sqrt(x)大的数能除尽的话,就必然存在一个比 sqrt(x)小的数能被除尽。请详细说明这句话?

==》任何一个合数都能够表示成n=p*q;其中p,q都不为1;且其中至少有一个<=根号sqrt(n)。

for(int i=2;i*i<=num;i++){
​
}

2.我们可以如何一次性快速获取大量的质数?(例如获取[2,100000]区间内的质数)

==》可以用埃氏筛法。该算法的核心思想是:通过从小到大遍历每个数,如果它是质数,则将其所有倍数标记为合数。这样,最终没有被标记的数就是质数。

例如:如果找到2是质数;则后面2的整数倍就都可以筛掉;

具体做法是:

定义了一个大小为10001的数组num,用于标记每个数是否为质数 , 初始时,数组中的所有元素都为0,表示这些数还没有被筛掉(即默认都是质数)。

如果i是质数,内层循环从2*i开始,将所有i的倍数标记为合数(将num[j]设为1)

  • i=2时,标记4、6、8、10……为合数。

  • i=3时,标记6、9、12、15……为合数。

int main(){
    
vector<int> num(10001);
//num[i]为0,说明还没有被筛掉
for(int i=2;i<=10000;i++){
    if(num[i]==0){
        printf("%d,\n",i);
    }
    for(int j=2*i;j<=10000;j+=i){
        num[j]=1;//说明j已经被筛掉了
    }
}
    return 0;
}

1.判断素数

原作

题目:用户登录 - N诺计算机考研

输入一个整数,判断该整数是否为素数,若是,输出该整数,若否,输出大于该整数的第一个素数。(例如,输入为 14,输出 17,因为 17 是大于 14 的第一个素数)

输入:一个整数 n,n 最大为 10000。

输出:按题意输出。

bool isPrime(int num) {
    
    for (int i = 2; i * i <= num; i++) {
        if (num %i== 0){
            return false;
        }
    }
    return true;
}
int main(){
//一个整数 n,n 最大为 10000。
    int n;
    scanf("%d",n);
    if(isPrime(n)){
        //若是,输出该整数
        printf("%d\n",n);
    }else{
        for(int i=num;i<=10000;i++){
            if(is(prime)){
                printf("%d\n",i);
                break;
            }
        }
    }
    return 0;
}
代码分析

上述代码动的通过率只有80%,我们需要做以下的修改:

1.上面的判断素数isPrime(int num),要补充上:

if (num <= 1) {
        return false;
    }

2.当 n 为 10000 时,我们需要寻找大于 10000 的素数,但由于上面for循环的循环条件是 i <= 10000,所以不会找到任何素数,导致程序没有输出。故在这里我们把它去掉。如下所示:

for (int i = n+1; ; i++) {
            if (isPrime(i)) {
                printf("%d\n", i);
                break;
            }
        }

3.拓展:用这个素数判定的方法去判定每一个数是不是素数的话。复杂度是 O(n*sqrt(n)),大概能处理到 10000 以内的数。

2.素数区间判定个数

原作

给你两个数 a、b,现在的问题是要判断这两个数组成的区间内共有多少个素数

输入:多组测试数据。 每个测试数据输入两个数 a、b。(2<=a,b<=1000)

输出:输出该区间内素数的个数。

以下代码是错误的:(反面教材)

int main(){
    int a,b;
    while(scanf("%d%d",&a,&b)!=EOF){
        int arr[1002];
        int count=0;
        for(int i=a;i<=b;i++){
            //如果是素数;则置为0
            if(arr[i]==0){
                count++;
            }
            for(int j=2*i;j<=10000;j+=i){
                arr[j]=1;
            }
        }
    }
    return 0;
}
代码分析

上述代码的通过率为0%,我们需要做以下的修改:

1.上述的数组未初始化

 // 初始化标记数组,0 表示素数,1 表示非素数
        int arr[1001] = {0};

2.因为题目中说a 和 b 的大小关系不定即 a 可能大于 b,也可能小于 b。

而上述代码假设 a <= b,如果输入中 a > b,会导致统计区间错误。

==》使用 if (a > b) { swap(a, b); } 确保 a <= b,从而正确统计区间 [a, b] 内的素数。

故正确代码为:

#define  _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <vector>
#include<cstdio>
using namespace std;
​
int main() {
    int a, b;
    while (scanf("%d%d", &a, &b) != EOF) {
        int arr[1001] = { 0 };
        //先去筛选素数
        if (a > b) {
            swap(a, b);
        }
        for (int i = 2; i <= b; i++) {
            //如果是素数;则置为0
            if (arr[i]==0){
                for (int j = 2 * i; j <= 1000; j += i) {
                    arr[j] = 1;
                }
            }
        }
        int count = 0;
        //再统计素数有多少个
        for (int i = a; i <= b; i++) {
            if (arr[i] == 0) {
                count++;
            }
        }
        printf("%d\n", count);
    }
    return 0;
}

3.埃氏筛法应用

题目

4520. 质数 - AcWing题库

输入:第一行包含一个整数 T,表示共有 T组测试数据。

每组数据占一行,包含一个整数 X。

1≤T≤100 1≤X≤100

输出: 每组数据输出一行结果,一个整数result,表示所得的满足条件的最小质数。

处理:在 X 后面添加若干位数字(至少添加一位数字;添加的数不能有前导0),使得结果为质数,在这个前提下所得的结果应尽量小。

代码:

int main() {
    int t;
    scanf("%d", &t);
    for (int i = 0; i < t; i++) {
        int x = 0;
        scanf("%d", &x);
        //处理:
        for (int j = 0; j < 1229; j++) {
            //10*x+1~10*x+9
            if (prime[j] >= 10 * x + 1 && prime[j] <= 10 * x + 9) {
                printf("%d\n", prime[j]);
                break;
            }
            //100*x+10~100*x+99
            if (prime[j] >= 100 * x + 10 && prime[j] <= 100 * x + 99) {
                printf("%d\n", prime[j]);
                break;
            }
        }
    }
    return 0;
}
代码分析

1.上面for循环中的1229是因为我们生成的2-10000之间的质数有这么多个。故这里遍历些质数,以找到符合要求的数,

for (int j = 0; j < 1229; j++)

2.这里要积累的套路是:

给定一个X,要求在其 后面添加若干位数字(至少添加一位数字;添加的数不能有前导0),使得结果为质数。要找到所得的满足条件的最小质数。

我们遍历了我们生成的10000之内的质数,并使用了两个for循环来模拟:

10x+1~10x+9

100x+10~100x+99

即获取到满足这个范围的质数;

同余

所谓的同余,顾名思义,就是许多的数被一个数 d 去除,有相同的余数.

这里的d叫做模。

举个例子说明一下同余的情况:

时钟上的小时数,都小于 12,所以小时数都是模 12 的同余。

应用:

(a+b)%c=(a%c+b%c)%c;

(a-b)%c=(a%c-b%c)%c;

(ab)%c=(a%cb%c)%c

问题:对于(a+b)%c=(a%c+b%c)%c; 为什么在等式右边,(a%c+b%c)之后还要再%c?

===>a%c+b%ca%c+b%c 的结果可能大于 c,因此需要再次取模 c来确保结果在 [ [0,c−1] 范围内。

例如: 设 a=7,b=8,c=5。 计算左边: (a+b)%c=(7+8)%5=15%5=0 计算右边: (a%c+b%c)%c=(7%5+8%5)%5=(2+3)%5=5%5=0 结果一致。

求S(n)

S(n)=n^5 ,求 S(n)除以 3 的余数

输入:每行输入一个整数 n,(0 < n < 1000000) 处理到文件结束

输出:输出 S(n)%3 的结果并换行

int main(){
    long long int n;
    while(scanf("%lld",&n)!=EOF){
            long long int s=n;
            printf("%lld\n", (n % 3) * (n % 3) * (n % 3) * (n % 3) * (n % 3) % 3);
    }
    return 0;
}
代码分析

n 虽然不大,但是 n^5 却超过 long long 的范围,所幸的

是题目只要我们对答案%3,这时候我们就可以运用同余模定理。

S(n)%3=(n^5)%3=(nnnnn)%3=((n%3)(n%3)(n%3)(n%3)(n%3))%3

最大公约数GCD

模板

  • 欧几里得算法的核心思想是:两个整数的最大公约数等于其中较小的数和两数相除余数的最大公约数。具体来说,对于两个整数 ab,如果 b 不为 0,那么 gcd(a, b) 等于 gcd(b, a % b)。这个过程会不断缩小问题的规模,直到 b 为 0,此时 a 就是最大公约数

故我们下面的模板的逻辑是:

  • 参数ab 是两个整数,表示需要计算最大公约数的两个数。

  • 递归终止条件:当 b 为 0 时,函数返回 a。这是因为如果 b 为 0,那么 a 就是最大公约数。

  • 递归调用:如果 b 不为 0,函数会递归调用自身,传入的参数是 ba % b(即 a 除以 b 的余数)。这个过程会一直持续,直到 b 为 0,递归终止。

int gcd(int a,int b){
    if(b==0){
    return a;
    }else{
        return gcd(b,a%b);
    }
}
​
int main(){
    int x,y;
    scanf("%d%d",&x,&y);
    printf("%d\n",gcd(x,y));
    return 0;
}
​

最简真分数

题目:

N诺会员专属特权

给出 n 个正整数,任取两个数分别作为分子和分母组成最简真分数,编程求共有几个这样的组合。

输入:每组 n(n<=600)和 n 个数,整数大于 1 且小于等于 1000。

输出:每行输出最简真分数组合的个数。

处理:判断在这n个数中任取两个数分别作为分子和分母能够组成最简真分数的组合有几个。

思路:

如果两个数分别作为分子和分母能够组成最简真分数,他们得满足什么条件?

===》

最简真分数必须同时满足两个条件:

  1. 分子小于分母(真分数)。

  2. 分子和分母的最大公约数为 1(最简分数)

故我们下面可以执行双循环来获取这些数。

int gcd(int a, int b) {
    if (b == 0) {
        return a;
    }
    else {
        return gcd(b, a % b);
    }
}
​
int main() {
    int n;
    int arr[602];
    while (scanf("%d", &n) != EOF) {
        for (int i = 0; i < n; i++) {
            scanf("%d", &arr[i]);
        }
        int ans = 0;
        //处理
        //判断在这n个数中任取两个数分别作为分子和分母能够组成最简真分数的组合有几个
        for (int i = 0; i < n; i++) { 
            for (int j = i + 1; j < n; j++) {
                if (gcd(arr[i], arr[j]) == 1) {
                    ans++;
                }
            }
​
        }
        printf("%d\n", ans);
    }
    return 0;
}

代码分析

1.gcb的参数是不是必须保证a>b,我们传进去gcd(buf[i],buf[j])有没有问题?

==>gcd 函数的参数不需要保证 a > b:

欧几里得算法的核心思想是:gcd(a, b) = gcd(b, a % b)。

如果 a < b,第一次递归调用时,a % b 会等于 a(因为 a 小于 b),然后函数会继续计算 gcd(b, a)。即即使 a < b,递归调用时会自动交换参数。因此,gcd 函数的参数顺序不影响最终结果。

结论:gcd(buf[i], buf[j]) 的传参没有问题,即使 buf[i] < buf[j],函数也能正确计算最大公约数。

2.以上代码是不是得保证输入的数据是从小到大排序的?它是怎么保证分子<分母的?

==>

上述通过内层循环的起始条件(j = i + 1)保证了分母始终在分子之后,因此不需要对输入数据进行排序。即使输入数据是乱序的,内层循环仍然会正确遍历所有可能的分子和分母组合。

分数化简

对于最公约数还有一种考法,就是进行分数化简。例如:给你一个分数 12/30,让你将它化简,很明显,我们都知道它的答案是 2/5。 那么:如果给你一个分数 x/y 呢?如何化简?

===》x/y = (x/gcd(x,y))/(y/gcd(x,y)),即求出他们的最大公约数,然后除一下就可以得到答案了。

最小公倍数(LCM)

对于求两个数的最小公倍数,我们可以使用下面的公式:

LCM(x, y) = x * y / GCD(x, y)

即:两个数的最小公倍数等于两个数的乘积除以两个数的最大公约数

上面的式子经过变形,易得下面的公式:

x * y = LCM(x, y) * GCD(x, y)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值