目录
[例题6: 二分的应用](#例题6: 二分的应用)
本章重点:
- 欧几里得算法
- 找规律
- 快速幂运算
- 二分查找
- 三分查找
导引问题:整数求和
任务:给定一个正整数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;
}
其中,实数Y满足(fabs(Y)<= 1e10)请输出x的区间[0, 100]的解,精确到小数点后4位。(输入Y求x)
其中,实数y满足(0 < y < 1e10)请输入x在区间[0, 100]时函数F(x)的最小值,结果精确到小数点后4位。(输入y求x)