[关闭]
@Dmaxiya 2025-01-16T18:39:20.000000Z 字数 4240 阅读 1654

小知识点


Hello_World


预处理

一个问题需要对某些值进行多次询问,如果能将询问的时间复杂度下降至 或者 ,则尽量将这个时间复杂度减下来,这就需要预处理,下面来介绍三个简单的预处理姿势。

sort 函数的使用

函数内部实现是快速排序,头文件为 algorithm。其功能是将传入的数组进行快速排序,时间复杂度为 ,空间复杂度为 。快排足以应对大多数题目。

这里介绍一下几种简单的 函数的用法。

1. 对基本数据类型的数组进行默认排序

sort 函数的默认排序规则是从小到大,如果对基本数据类型数组进行排序,则只要将数组的首地址作为函数的第一个实参,超尾地址作为函数的第二个实参,就可以对数组从第一个参数指向的地址到第二个参数指向的地址之间的元素进行排序。

超尾地址:数组最后一个元素的后一位,例如对于声明为

  1. const int maxn = 100;
  2. int num[maxn];

的整型数组,最后一个元素为

  1. num[maxn - 1];

其超尾地址就是

  1. &num[maxn];

也可以写为

  1. num + maxn;

要对数组所有元素进行从小到大排序,sort 函数的调用方式为

  1. sort(num, num + maxn);

如果以

  1. sort(num, num + n);

调用 sort 函数,则对 num 数组的前 个元素从小到大排序,示例代码如下:

  1. #include <cstdio> // printf 的头文件
  2. #include <algorithm> // sort 的头文件
  3. using namespace std; // 命名空间
  4. int main() {
  5. int num[] = {3, 2, 1, 5, 7};
  6. sort(num, num + 5); // 默认从小到大排序
  7. for(int i = 0; i < 5; ++i) {
  8. printf("%d ", num[i]); // 输出排序后的数组
  9. }
  10. return 0;
  11. }

2. 对基本数据类型按特定规则排序

sort 函数的另一个重载形式,可以支持第三个参数:比较函数的传入,它会将比较函数应用在快速排序中,当比较函数返回值为 true / 非零值时,第一个元素被认为比第二个元素小,当比较函数返回值为 false / 时,第一个元素被认为大于等于第二个元素,比较函数可以自定义,但是一定要注意:比较函数必须要定义小于号的意义,而不能定义小于等于号的意义。

以下是对 num 整型数组所有元素进行绝对值从小到大排序的比较函数定义与 sort 函数的调用方法示例:

  1. const int maxn = 100;
  2. int num[maxn];
  3. bool cmp(int a, int b) {
  4. return abs(a) < abs(b);
  5. }
  6. sort(num, num + maxn, cmp);

前缀和

对于大小为 的数组,定义

代码:

  1. for(int i = 1; i <= n; ++i) {
  2. sum[i] = sum[i - 1] + num[i];
  3. }

杨辉三角

定义1:

定义2:

代码:

  1. C[0][0] = 1;
  2. for(int i = 1; i <= n; ++i) {
  3. for(int j = 0; j <= i; ++j) {
  4. if(j == 0 || j == i) {
  5. C[i][j] = 1;
  6. } else {
  7. C[i][j] = C[i - 1][j - 1] + C[i - 1][j];
  8. }
  9. printf("C[%d][%d] = %d\n", i, j, C[i][j]);
  10. }
  11. }

离散化

对于某些数据范围比较大,但是要用数组模拟的题目,第一种方法可以用 map 处理,但是 map 的询问比数组多一个 ,如果数值相对的大小关系比较重要,而数本身的值并不重要的话,可以用离散化来对数据进行预处理,达到减小空间复杂度和时间复杂度的目的。

例如,将数组

中各个元素的值离散化,得到新数组

代码:

  1. const int maxn = 100;
  2. int n = 10;
  3. int a[maxn] = {555, 422, 72, 24, 24, 24, 422, 54, 68, 142};
  4. int b[maxn];
  5. vector<int> nums;
  6. for (int i = 0; i < n; ++i) {
  7. nums.push_back(a[i]);
  8. printf("%5d", a[i]);
  9. }
  10. printf("\n");
  11. sort(nums.begin(), nums.end());
  12. nums.erase(unique(nums.begin(), nums.end()), nums.end());
  13. for (int i = 0; i < n; ++i) {
  14. b[i] = lower_bound(nums.begin(), nums.end(), a[i]) - nums.begin();
  15. printf("%5d", b[i]);
  16. }
  17. printf("\n");

取模公式

加减法:
加法:

减法:

扩展:若 爆了 long long,但答案需要求出 的值,应该怎么办?有更快的方法吗?

乘除法:
乘法:
逆元:若 ,则 相对于 的逆元
记为
除法:

扩展:

1. 欧拉函数

将对于任意正整数 ,在区间 内,与其最大公约数为 的整数的个数,定义为 的欧拉函数

对于任意正整数 的求法为:

其欧拉函数

其中 分别为能整除 个不同的质数。

2. 逆元

当且仅当 时, 存在,此时

二分答案

如果答案满足下面的性质,则可以考虑用二分答案来解决:

  1. 答案为整数
  2. 答案具有单调性,单调非递增或单调非递减
  3. 求最小值的最大值,或者最大值的最小值
  4. 验证答案比直接求解答案更容易

二分有两种写法,一种左闭右开,一种左开右闭,即

  1. 二分的答案满足条件(或 judge 函数返回 true)时,需要查找更大值,这时需要用左闭右开的写法,若采用这种写法, 应初始化为答案的下界, 应初始化为答案的上界 ,最后的答案为
  2. 二分的答案满足条件时,需要查找更小的值,这时需要用左开右闭的写法,若采用这种写法, 应初始化为答案的下界 应初始化为答案的上界,则最后的答案为

lower_bound 与 upper_bound 实现代码:

  1. // lower_bound 函数返回首地址为 begin, 超尾地址为 end 的有序数组中,第一个 >= num 值的地址
  2. int* lower_bound(int *begin, int *end, int num) {
  3. int low = -1;
  4. int high = end - begin;
  5. int mid;
  6. while(high - low > 1) {
  7. mid = (low + high) / 2;
  8. if(begin[mid] >= num) {
  9. high = mid;
  10. } else {
  11. low = mid;
  12. }
  13. }
  14. return begin + high;
  15. }
  16. // upper_bound 函数返回首地址为 begin,超尾地址为 end 的有序数组中,第一个 > num 值的地址
  17. int* upper_bound(int *begin, int *end, int num) {
  18. int low = -1;
  19. int high = end - begin;
  20. int mid;
  21. while(high - low > 1) {
  22. mid = (low + high) / 2;
  23. if(begin[mid] > num) {
  24. high = mid;
  25. } else {
  26. low = mid;
  27. }
  28. }
  29. return begin + high;
  30. }
  31. const int maxn = 100;
  32. int n = 10;
  33. int a[maxn] = {2, 3, 3, 4, 6, 6, 6, 7, 8, 9};
  34. for (int i = 0; i <= 10; ++i) {
  35. printf("lower_bound %2d at %2d\n", i, lower_bound(a, a + n, i) - a);
  36. printf("upper_bound %2d at %2d\n", i, upper_bound(a, a + n, i) - a);
  37. }

并查集

并查集是通过修改祖先节点,判断两个节点的祖先是否相同,来判断两个点是否在同一个连通块的数据结构,其时间复杂度为 。除了初始化 Init,并查集有两种操作:询问祖先 Find 与合并两个节点到同一个连通块中 unit

代码:

  1. const int maxn = 10000 + 100;
  2. int fa[maxn];
  3. void Init() {
  4. for (int i = 0; i < maxn; ++i) {
  5. fa[i] = i;
  6. }
  7. }
  8. int Find(int x) {
  9. return x == fa[x]? x: fa[x] = Find(fa[x]);
  10. }
  11. void unit(int x, int y) {
  12. fa[Find(x)] = Find(y);
  13. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注