数学函数

数学相关算法

1. 根据数据范围猜算法

算法数据范围初定:单数据 10 的 9 次方以内无脑用 int,单数据 10 的 18 次方以内无脑 long long,更高的数据量需要考虑 long double(10 的 308 次方左右),但是注意:long double 虽然能表示很大的数,但精度不够稳定,误差较大!对准确性要求极高的场景下,就不太靠谱了。这时就需要考虑高精度算法和计算库了。还有在多数据涉及加法和乘法操作时,注意边界溢出!

1. 不同数据规模下可接受的算法时间复杂度

数据规模O(logn)O(√n)O(n)O(n×logn)O(n²)O(n³)O(2ⁿ)O(n!)
n ≤10
n ≤30
n ≤100
n ≤10³
n ≤2×10⁵
n ≤10⁷
n ≤10⁹
n ≤10¹⁸

2. 常见算法及其典型时间复杂度

只列举一下常用的,等学到那些比较难的算法的时候,这个文档的内容都应该已经印到脑子里了~

时间复杂度典型算法或场景
O(n!)递归、DFS、DFS + 剪枝
O(2ⁿ)递归、DFS、DFS + 剪枝
O(n³)动态规划、记忆化搜索、floyd (多源最短路)
O(n²)动态规划、记忆化搜索、dijkstra (单源最短路)、prim (最小生成树)
O(n×logn)快排、归并、heap (堆)、set (红黑树)、二分、拓扑排序、堆优化的 dijkstra、堆优化的 prim、线段树
O(n)双指针、滑动窗口、单调栈和单调队列、搜索 (BFS+DFS)、并查集
O(√n)判断质数、因数分解
O(logn)辗转相除法 (最大公约数),快速幂

提示:算法竞赛或面试中,先看数据范围 $n$,再反推允许的时间复杂度,能快速排除不可行方案,聚焦正确解法。

2. 基本数学函数(<cmath>

功能函数原型使用示例注意事项
平方根double sqrt(double x)sqrt(16.0) = 4.0参数需非负
幂运算 (b e )double pow(double b, double e)pow(2.0, 3.0) = 8.0效率低于位运算
浮点数绝对值double abs(double x)abs(-3.14) = 3.14整数用 <cstdlib>abs()
浮点数绝对值double fabs(double x)fabs(-2.5) = 2.5abs()
向上取整double ceil(double x)ceil(2.3) = 3.0
向下取整double floor(double x)floor(2.7) = 2.0
四舍五入double round(double x)round(2.5) = 3.0C++11
浮点数取模double fmod(double a, double b)fmod(5.5, 2.0) = 1.5余数 = a - b*trunc(a/b)
欧几里得距离 (√(x²+y²))double hypot(double x, double y)hypot(3,4) = 5.0避免溢出
自然对数 (ln x)double log(double x)log(exp(1)) ≈ 1.0x > 0
常用对数 (log₁₀ x)double log10(double x)log10(100) = 2.0x > 0

3. 数值算法(<algorithm> & <numeric>

功能函数原型使用示例注意事项
最大值T max(T a, T b)max(3, 5) = 5支持多参数 max({a,b,c})
最小值T min(T a, T b)min(2.0, 1.5) = 1.5支持多参数
绝对值T abs(T x)abs(-10) = 10模板版本
最大公约数int gcd(int a, int b) (C++17)gcd(12, 18) = 6C++17 需 <numeric>
最小公倍数int lcm(int a, int b) (C++17)lcm(4, 6) = 12C++17 需 <numeric>
区间求和T accumulate(It first, It last, T init)accumulate(v.begin(), v.end(), 0)<numeric>
填充递增序列void iota(It first, It last, T value)iota(v.begin(), v.end(), 1)<numeric>,生成 1,2,3,…
1
2
3
4
5
6
7
8
9
10
// 对于最大公约数和最小公倍数,如果环境不支持,可以手动实现其两者功能:
int gcd(int a, int b)
{
return b == 0 ? a : gcd(b, a % b);
}

int lcm(int a, int b)
{
return (a * b) / gcd(a, b);
}

C++17 及以上建议使用标准库的 gcd()lcm() 函数。

4. 常见相关常量(也在 <climits> 中)

注意事项:

  • 不要混淆 <climits>(C++) 和 <limits.h>(C)。
  • 如果你用的是 C++,推荐使用 <climits>
  • INT_MAX 是编译器根据系统架构定义的,保证可移植性。
功能
int 最大值(通常是 2³¹−1)INT_MAX
int 最小值(通常是 -2³¹)INT_MIN
long long 最大值LLONG_MAX
long long 最小值LLONG_MIN
大概是 INT_MAX 的一半,本质是一个 16 进制数,常用于防溢出0x3f3f3f3f(f 可大写可小写)
unsigned int 最大值UINT_MAX
char 最大值CHAR_MAX
long 最大值LONG_MAX
short 最大值SHRT_MAX

定义 π 常量

  1. 传统数学函数计算法(C++11 及以上都支持):

    • acos(-1.0) 计算的是余弦值为 - 1.0 的角度,这个角度正好是 π 弧度(180 度),这种方式不依赖于任何特定的库或编译器扩展。
    • 优点:兼容性好,不依赖特定编译器或标准库版本。
    • 缺点:需要包含 <cmath> 头文件。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    #include <cmath>

    // 最常用的方式
    const double PI = acos(-1.0);

    // 其他等效方式
    const double PI = atan(1.0) * 4;
    const double PI = asin(1.0) * 2;
    const double PI = M_PI; // 注意:这不是标准 C++,但很多编译器支持
  2. 直接数值定义法:

    • 优点: 不需要任何头文件,计算最快。
    • 缺点: 精度有限,需要手动输入足够多的小数位。
    1
    2
    3
    4
    5
    6
    7
    8
    // 单精度
    const float PI = 3.14159265358979323846f;

    // 双精度
    const double PI = 3.14159265358979323846;

    // 长双精度
    const long double PI = 3.14159265358979323846264338327950288L;
  3. C++11 constexpr 方式:

    • 优点: 编译期计算,不占用运行时资源。
    • 缺点: 需要 C++11 及以上标准,且 acos 在 constexpr 中的支持因编译器而异。
    1
    2
    #include <cmath>
    constexpr double PI = acos(-1.0);
  4. C++20 标准库方法(推荐):

    • 优点: 标准、精确、类型安全。
    • 缺点: 需要 C++20 及以上标准。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    #include <numbers>

    // 使用 double 精度
    const double PI = std::numbers::pi;

    // 指定精度
    const float PI_f = std::numbers::pi_v<float>;
    const double PI_d = std::numbers::pi_v<double>;
    const long double PI_ld = std::numbers::pi_v<long double>;

5. 随机数(<cstdlib>

随机数是竞赛中的骗分小技巧,正确使用会有意想不到的效果,只能作为最后挣扎的手段。 当然,随机数存在随机化搜索、随机贪心算法、蒙特卡洛方法、随机化哈希等一系列高级用法,但是涉及到这些的题目基本就不是普通人能做的了,咱就图一乐,会 控制 随机数骗一点分已是很了不起了!

功能函数原型使用示例注意事项
生成伪随机数int rand()rand() % 100范围 [0, RAND_MAX]
设置随机种子void srand(unsigned seed)srand(time(0))<ctime>
1
2
3
4
5
6
7
8
9
10
11
#include <cstdlib>
#include <ctime>

// 初始化随机种子
srand(time(0));

// 生成随机数
int rand_num = rand(); // [0, RAND_MAX]
int rand_0_99 = rand() % 100; // [0, 99]
int rand_1_100 = rand() % 100 + 1; // [1, 100]
int rand_a_b = rand() % (b - a + 1) + a; // [a, b]

6. 位运算(GCC 内置)

说实话位运算用得不多,但用的好效率很高,人也是真厉害!注意:竞赛中常用 GCC 编译器,可使用 __builtin 系列函数优化位运算操作。

1. 基础位运算操作

运算符名称功能说明示例
&按位与两个位都为 1 时,结果为 15 & 3 = 1 (101 & 011 = 001)
``按位或两个位都为 0 时,结果为 0
^按位异或两个位不同时,结果为 15 ^ 3 = 6 (101 ^ 011 = 110)
~按位取反0 变 1,1 变 05 = -6 (101 = …11111010)
<<左移各二进制位向左移动,右边补 05 << 2 = 20 (101 << 2 = 10100)
>>右移各二进制位向右移动,左边补符号位5 >> 2 = 1 (101 >> 2 = 001)

2. 位运算函数

功能函数原型使用示例返回值
二进制中 1 的个数int __builtin_popcount(unsigned int x)__builtin_popcount(7) = 332 位整数
前导 0 的个数int __builtin_clz(unsigned int x)__builtin_clz(1) = 3132 位整数(MSB 开始)
后缀 0 的个数int __builtin_ctz(unsigned int x)__builtin_ctz(8) = 332 位整数(LSB 开始)
最低位 1 的位置int __builtin_ffs(int x)__builtin_ffs(6) = 2位置从 1 开始计数

3. 常用位运算技巧

1. 判断是否为 2 的幂次

1
2
3
4
bool isPowerOfTwo(int n)
{
return n > 0 && (n & (n - 1)) == 0;
}

2. 判断奇偶性

1
2
3
4
5
6
7
8
9
bool isEven(int x)
{
return (x & 1) == 0; // 偶数
}

bool isOdd(int x)
{
return (x & 1) == 1; // 奇数
}

3. 统计二进制中 1 的个数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int countSetBits(int n)
{
int count = 0;
while (n)
{
count++;
n &= n - 1; // 清除最低位的 1
}
return count;
}

// 或者使用 GCC 内置函数
int countSetBits(int n)
{
return __builtin_popcount(n);
}

4. 计算二进制中 0 的个数

1
2
3
4
int countZeroBits(int n, int bits = 32)
{
return bits - __builtin_popcount(n);
}

5. 清除最低位的 1

1
2
3
4
int clearLowestBit(int n)
{
return n & (n - 1);
}

6. 获取最低位的 1

1
2
3
4
int getLowestBit(int n)
{
return n & -n;
}

7. 三角函数(<cmath>

功能函数原型使用示例注意事项
正弦double sin(double rad)sin(M_PI/2) = 1.0参数为弧度(非角度)
余弦double cos(double rad)cos(M_PI) = -1.0需定义 #define M_PI 3.1415926535
正切double tan(double rad)tan(M_PI/4) ≈ 1.0
反正弦double asin(double x)asin(1.0) = M_PI/2返回值 ∈ [-π/2, π/2]
反余弦double acos(double x)acos(-1.0) = M_PI返回值 ∈ [0, π]
反正切double atan(double x)atan(1.0) = M_PI/4返回值 ∈ [-π/2, π/2]