算法入门数学基础

目录

例题1:辗转相除法(欧几里得算法)

例题2:n的n次方求个位

例题3:斐波那契规律题

例题4:快速幂

快速幂的模版:

例题5:二分查找

二分查找模版:

[例题6: 二分的应用](#例题6: 二分的应用)

例题7:三分查找


本章重点:

  • 欧几里得算法
  • 找规律
  • 快速幂运算
  • 二分查找
  • 三分查找

导引问题:整数求和

任务:给定一个正整数n,计算1+2+3+....+n的结果,其中n <= 50000。

代码1:使用循环累加

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main() {
	int n = 0;
	int sum = 0;
	scanf("%d", &n);
	int i = 0;
	for (i = 0; i <= n; i++) {
		sum += i;
	}
	printf("%d\n", sum);
	return 0;
}

方法2:使用高斯公式:n +(n+ 1) / 2

那么问题来了,我们知道题目的范围n <= 50000。如果这里我们假设n = 50000。如果使用高斯公式50000 * 50001 = 2,500,050,000超过了int类型。这里就需要使用long long。那么可以不使用long long 来计算吗?

解决方案1:使用long long。%lld 8字节64bit。

解决方案2:先除后乘 这里要考虑奇偶性,做一个判断。

例题1:辗转相除法( 欧几里得算法**)**

任务:给定两个正整数,计算这两个数的最小公倍数(能同时被两个数整除的最小数)。

样例输入:

10 14

4 6

样例输出:

70

12

方法1:暴力枚举

方法2:从大数开始枚举大数的倍数(枚举的改进)。

方法3:辗转相除法。

公式:LCM(A,B) = A * B / GCD(A, B)

上面公式解释A 和 B 的最小公倍数 = A * B / (A和B的最大公约数)。

注意:上面公式可以会出现导引中提到过的一个问题 int 类型可能会爆。所以也要进行转换先除后乘, 或者使用long long类型。

这里以10 和 14 作为例子进行说明求解过程:具体过程看上图。

cpp 复制代码
#include<stdio.h>

//辗转相除法求gcd
int gcd(int da, int xiao) {
	int tep;
	while (xiao != 0) {
		tep = da % xiao;
		da = xiao;
		xiao = tep;
	}
	return da;
}

int main() {
	//求a和b的最小公倍数
	int a = 14;
	int b = 10;
	int ans = a / gcd(a, b)*b;
	printf("%d \n", ans);
	return 0;
}

思考:大小两个参数位置是否可以变?答案可以,循环一次就能纠正过来,多做一次循环。

例题2:n的n次方求个位

任务:给定一个整数N,请计算N个N相乘的结果的个位数是都少(1 <= N <= 1000)。

方法1:暴力。这显然是不行的原因N的N次方太大了long long类型都不能放下。

方法2:做n - 1趟循环每次都取出个位数。为什么只取出个位就可以?原因:因为结果要的是个位所以其他位的不会影响个位的结果。

cpp 复制代码
#include<stdio.h>

int main() {
	int N = 0;
	scanf("%d", &N);
	int i = 0;
	int ans = N;
	for (i = 0; i < N - 1; i++) {
		ans =  N * ans;
		ans = ans % 10;
	}
	printf("%d", ans);
	return 0;
}

方法3:抽屉原理 ,找规律。因为个位数 只能有10种情况。在很多题目中会出现循环节

抽屉原理: 桌上有十个苹果,要把这十个苹果放到九个抽屉里,无论怎样放,我们会发现至少会有一个抽屉里面放不少于两个苹果。这一现象就是我们所说的"抽屉原理"。 抽屉原理的一般含义为:"如果每个抽屉代表一个集合,每一个苹果就可以代表一个元素,假如有n+1个元素放到n个集合中去,其中必定有一个集合里至少有两个元素。" 抽屉原理有时也被称为鸽巢原理。它是组合数学中一个重要的原理。

例题3:斐波那契规律题

有一种fibonacci数列,定义如下:F(0) = 7,F(1) = 11, F(n) = F(n - 1) + F(n - 2) (n >= 2)。给定一个n(n <= 1 000 000),请判断F(n)能否被3整除,分别输出yes和no。

分析这里的n太大了暴力绝对不可行。那么怎么去考虑呢?

改良暴力:通过上面的地推式推出:F(n) % 3 = (F(n - 1) % 3 + F(n - 2) % 3) % 3

打表找规律:

cpp 复制代码
#include<stdio.h>

int main() {
	//int arr[100];
	//arr[0] = 7 % 3;
	//arr[1] = 11 % 3;
	//int i = 0;
	//
	//for (i = 2; i < 100; i++) {
	//	int ant = (arr[i - 1] % 3 + arr[i - 2] % 3) % 3;
	//	arr[i] = ant;
	//}
	//for (i = 0; i < 100; i++) {
	//	printf("%d ", arr[i]);
	//}
	//通过打表没8个为一个循环节
	//1 2 0 2 2 1 0 1 //3 7

	int N;
	scanf("%d", &N);
	if ((N % 8) == 3 || (N % 8) == 7) {
		printf("%s\n", "yes");
	}
	else {
		printf("no");
	}

	return 0;
}

例题4:快速幂

任务:求A^B的最后三位数表示的整数(1 <= A, B <= 10000)。

样例输入:

2 3

12 6

样例输出:

8

984

方法1:直接暴力; 出现的问题数据太大可能会使int 或者long long爆掉。

方法2:改进的暴力。这里的A, B都小于10000可以使用暴力。如果数据更大该怎么办,看方法3。

cpp 复制代码
#include<stdio.h>

int main() {
	int A;
	int B;
	scanf("%d %d", &A, &B);
	int ans = 1;
	int i = 0;
	for (i = 0; i < B; i++) {
		ans *= A;
		ans %= 1000;  //题目只要最后三位数
	}
	printf("%d\n", ans);
	return 0;
}

方法3:快速幂

顾名思义,快速幂就是快速算底数的n次幂。其时间复杂度为 O(log₂N), 与朴素的O(N)相比效率有了极大的提高。

快速幂算法的核心思想就是每一步都把指数分成两半,而相应的底数做平方运算。这样不仅能把非常大的指数给不断变小,所需要执行的循环次数也变小,而最后表示的结果却一直不会变。

快速幂的模版:

cpp 复制代码
// 快速幂模版递归方式
int power(int a, int n) {
	int ans;
	if (n == 0) {
		ans = 1;
	}
	else {
		ans = power(a * a, n / 2);
		if (n % 2 == 1) {
			ans *= a;
		}
	}
	return ans;
}

//快速幂非递归方法
int power(int a, int n) {
	int ans = 1;
	while (n) {
		if (n % 2==1) {
			ans = ans * a;
		}
		a = a * a;
		n /= 2;
	}
    return ans;
}

快速幂模版取模版本:

取模的原因:是因为A^N次方一般特别大在算法题出现类似的题一般会取模。下面代码是对1000取模。

cpp 复制代码
// 快速幂模版递归方式
int power(int a, int n) {
	int ans;
	if (n == 0) {
		ans = 1;
	}
	else {
		ans = power((a * a) % 1000, n / 2);
		if (n % 2 == 1) {
			ans =(ans * a % 1000);
		}
	}
	return ans;
}

//快速幂非递归方法
int power(int a, int n) {
	int ans = 1;
	while (n) {
		if (n % 2==1) {
			ans = (ans * a) % 1000;
		}
		a = (a * a) % 1000;
		n /= 2;
	}
	return ans;
}

例题5:二分查找

给出若干个(可以很多)有序的整数,请查找某个元素是否存在,比如在1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20中查找15。如果找到返回元素的下标,找不到返回-1。

注意:二分查找的前提是------数据是有序的。

二分法搜索(Binary Search),又称折半搜索、对数搜索,是一种在有序数组中查找特定元素的搜索算法 。该算法时间复杂度为O(log n),迭代实现空间复杂度为O(1),递归实现则为O(log n)。

二分查找模版:

非递归模版

cpp 复制代码
//二分查找模版
int BiSearch(int a[], int n, int x) {
	/*arr数组 
	* n是数组的长度
	* x所要查找的数值
	*/
	int left = 0;
	int right = n - 1;
	while (left <= right) {				//注意:这里等号不能少
		int mid = (left + right) / 2;   //整数除法
		if (a[mid] == x) {				//找到的情况
			return mid;
		}
		if (x > a[mid]) {				//如果比查找的值大
			left = mid + 1;
		}
		else {
			right = mid - 1;			//如果比查找的值小
		}
	}
	return -1;  //没有找到返回-1
}

递归模版:

写递归时候一般先写递归出口

cpp 复制代码
//二分查找模版
int BiSearch(int a[], int x, int left, int right) {
	/*a数组 
	* x所要查找的数值
	*/
	if (left > right) {//递归出口
		return -1;
	}
	else {
		int mid = (left + right) / 2;
		if (a[mid] == x)
			return mid;
		else if (x > a[mid])
			return BiSearch(a, x, mid + 1, right);
		else
			return BiSearch(a, x, left, mid - 1);
	}
}

思考:在一百万个有序元素中用二分查找一个元素大约需要比较多少次?

答案:大约20次。

时间复杂度:O(logN)。

例题6: 二分的应用

给出方程:其中,实数Y满足(fabs(Y)<= 1e10)请输出x的区间[0, 100]的解,精确到小数点后4位。(输入Y求x)

cpp 复制代码
#include<stdio.h>
double f(double x){//题目中的式子
	return 8 * x*x*x*x + 7 * 
			   x*x*x + 2 * 
			   x*x + 3 * x + 6;
}
int main() {
	double Y;
	double left, right, mid;
	int t;
	scanf("%d", &t);//测试次数
	while (t--) {
		scanf("%lf", &Y);
		if (f(0) <= Y && Y <= f(100)) {
			left = 0;
			right = 100;
			while (right - left > 1e-6) {
				mid = (left + right) / 2;
				double ans = f(mid);
				if (ans > Y)
					right = mid - 1e-7;
				else
					left = mid + 1e-7;
			}
			printf("%.4f\n", (left + right) / 2);
		}
		else
			printf("No solution!\n");//没找到符合的结果
	}
	return 0;
}

例题7:三分查找

给定函数:其中,实数y满足(0 < y < 1e10)请输入x在区间[0, 100]时函数F(x)的最小值,结果精确到小数点后4位。(输入y求x)

方法1:求导找到倒数为0的点就是最小值

方法2:三分查找

三分查找 (Ternary Search)是一种用于在单峰函数(凸函数或凹函数)上高效逼近最大值或最小值的搜索算法,通过每次迭代将搜索区间三等分并排除无效部分,时间复杂度为O(log₃n)

三分查找:用来求极值点,和二分查找不同,二分查找比较的是所查找的值是否等于下标为mid元素的值,而三分查找比较的是LeftThird 和 RigthThird的Y值。

总结:二分查找要求单调性。

三分的前提------数据的凸凹性,这里的凸凹不要求单调。

非递归

cpp 复制代码
#include <stdio.h>
#include <math.h>
double pow(double a, int n) {
    double ans = 1;
    for (int i = 0; i < n; i++) {
        ans *= a;
    }
    return ans;
}

double f(double x, double y) {
    return 6 * pow(x, 7) + 8 * pow(x, 6) +
        7 * pow(x, 3) + 5 * pow(x, 2) - y * x;
}
int main() {
	double y;
	scanf("%lf", &y);
	double left = 0;
	double right = 100;
	//这里的循环结束标志是两个数的f函数的绝对值小于1e-7,因为要求保留4位小数
	double epsilon = 1e-7;
	while (fabs(right - left) > epsilon) {
		double leftThird = left + (right - left) / 3;
		double rightThird = right - (right - left) / 3;
		if (f(leftThird, y) < f(rightThird, y)) {
			right = rightThird - epsilon;
//这里是不需要考虑偏移的,原因是因为循环结束条件是绝对值小于1e-7,如果left == right也不会死循环
		}
		else {
			left = leftThird + epsilon;
		}
	}
	printf("%.4lf", f((left + right) / 2, y));
	return 0;
}

递归

cpp 复制代码
#include <stdio.h>
#include <math.h>

double pow(double a, int n) {
    double ans = 1;
    for (int i = 0; i < n; i++) {
        ans *= a;
    }
    return ans;
}

double f(double x, double y) {
    return 6 * pow(x, 7) + 8 * pow(x, 6) +
        7 * pow(x, 3) + 5 * pow(x, 2) - y * x;
}

double san(double y, double left, double right) {
    double epsilon = 1e-7;
    if (fabs(right - left) < epsilon) {
        return f((left + right) / 2, y);
    }

    double leftThird = left + (right - left) / 3;
    double rightThird = right - (right - left) / 3;

    if (f(leftThird, y) < f(rightThird, y)) {
        return san(y, left, rightThird - epsilon);
    }
    else {
        return san(y, leftThird + epsilon, right);
    }
}

int main() {
    double y;
    scanf("%lf", &y);
    double left = 0;
    double right = 100;
    double ans = san(y, left, right);
    printf("%.4lf", ans);
    return 0;
}
相关推荐
三川6982 小时前
排序算法介绍
数据结构·算法·排序算法
2301_795167205 小时前
玩转Rust高级应用 如何避免对空指针做“解引用”操作,在C/C++ 里面就是未定义行为
c语言·c++·rust
智驱力人工智能7 小时前
基于视觉分析的人脸联动使用手机检测系统 智能安全管理新突破 人脸与手机行为联动检测 多模态融合人脸与手机行为分析模型
算法·安全·目标检测·计算机视觉·智能手机·视觉检测·边缘计算
2301_764441337 小时前
水星热演化核幔耦合数值模拟
python·算法·数学建模
循环过三天7 小时前
3.4、Python-集合
开发语言·笔记·python·学习·算法
昌sit!9 小时前
Linux系统性基础学习笔记
linux·笔记·学习
学会沉淀。9 小时前
设备如何“开口说话”?
学习
priority_key10 小时前
排序算法:堆排序、快速排序、归并排序
java·后端·算法·排序算法·归并排序·堆排序·快速排序
不染尘.10 小时前
2025_11_7_刷题
开发语言·c++·vscode·算法
m0_5913389110 小时前
day10数组的学习
学习