@Dmaxiya
2025-01-16T10:39:20.000000Z
字数 4240
阅读 1866
Hello_World
一个问题需要对某些值进行多次询问,如果能将询问的时间复杂度下降至 或者 ,则尽量将这个时间复杂度减下来,这就需要预处理,下面来介绍三个简单的预处理姿势。
sort 函数的使用 函数内部实现是快速排序,头文件为 algorithm。其功能是将传入的数组进行快速排序,时间复杂度为 ,空间复杂度为 。快排足以应对大多数题目。
这里介绍一下几种简单的 函数的用法。
sort 函数的默认排序规则是从小到大,如果对基本数据类型数组进行排序,则只要将数组的首地址作为函数的第一个实参,超尾地址作为函数的第二个实参,就可以对数组从第一个参数指向的地址到第二个参数指向的地址之间的元素进行排序。
超尾地址:数组最后一个元素的后一位,例如对于声明为
const int maxn = 100;int num[maxn];
的整型数组,最后一个元素为
num[maxn - 1];
其超尾地址就是
&num[maxn];
也可以写为
num + maxn;
要对数组所有元素进行从小到大排序,sort 函数的调用方式为
sort(num, num + maxn);
如果以
sort(num, num + n);
调用 sort 函数,则对 num 数组的前 个元素从小到大排序,示例代码如下:
#include <cstdio> // printf 的头文件#include <algorithm> // sort 的头文件using namespace std; // 命名空间int main() {int num[] = {3, 2, 1, 5, 7};sort(num, num + 5); // 默认从小到大排序for(int i = 0; i < 5; ++i) {printf("%d ", num[i]); // 输出排序后的数组}return 0;}
sort 函数的另一个重载形式,可以支持第三个参数:比较函数的传入,它会将比较函数应用在快速排序中,当比较函数返回值为 true / 非零值时,第一个元素被认为比第二个元素小,当比较函数返回值为 false / 时,第一个元素被认为大于等于第二个元素,比较函数可以自定义,但是一定要注意:比较函数必须要定义小于号的意义,而不能定义小于等于号的意义。
以下是对 num 整型数组所有元素进行绝对值从小到大排序的比较函数定义与 sort 函数的调用方法示例:
const int maxn = 100;int num[maxn];bool cmp(int a, int b) {return abs(a) < abs(b);}sort(num, num + maxn, cmp);
对于大小为 的数组,定义
则
for(int i = 1; i <= n; ++i) {sum[i] = sum[i - 1] + num[i];}
定义1:
定义2:
C[0][0] = 1;for(int i = 1; i <= n; ++i) {for(int j = 0; j <= i; ++j) {if(j == 0 || j == i) {C[i][j] = 1;} else {C[i][j] = C[i - 1][j - 1] + C[i - 1][j];}printf("C[%d][%d] = %d\n", i, j, C[i][j]);}}
对于某些数据范围比较大,但是要用数组模拟的题目,第一种方法可以用 map 处理,但是 map 的询问比数组多一个 ,如果数值相对的大小关系比较重要,而数本身的值并不重要的话,可以用离散化来对数据进行预处理,达到减小空间复杂度和时间复杂度的目的。
例如,将数组
中各个元素的值离散化,得到新数组
const int maxn = 100;int n = 10;int a[maxn] = {555, 422, 72, 24, 24, 24, 422, 54, 68, 142};int b[maxn];vector<int> nums;for (int i = 0; i < n; ++i) {nums.push_back(a[i]);printf("%5d", a[i]);}printf("\n");sort(nums.begin(), nums.end());nums.erase(unique(nums.begin(), nums.end()), nums.end());for (int i = 0; i < n; ++i) {b[i] = lower_bound(nums.begin(), nums.end(), a[i]) - nums.begin();printf("%5d", b[i]);}printf("\n");
加减法:
加法:
若 :
减法:
扩展:若 爆了
long long,但答案需要求出 的值,应该怎么办?有更快的方法吗?
乘除法:
乘法:
逆元:若 ,则 是 相对于 的逆元
记为
除法:
扩展:
1. 欧拉函数
将对于任意正整数 ,在区间 内,与其最大公约数为 的整数的个数,定义为 的欧拉函数 。
对于任意正整数 , 的求法为:
若
其欧拉函数
其中 分别为能整除 的 个不同的质数。
2. 逆元
当且仅当 时, 存在,此时
如果答案满足下面的性质,则可以考虑用二分答案来解决:
二分有两种写法,一种左闭右开,一种左开右闭,即 与 。
// lower_bound 函数返回首地址为 begin, 超尾地址为 end 的有序数组中,第一个 >= num 值的地址int* lower_bound(int *begin, int *end, int num) {int low = -1;int high = end - begin;int mid;while(high - low > 1) {mid = (low + high) / 2;if(begin[mid] >= num) {high = mid;} else {low = mid;}}return begin + high;}// upper_bound 函数返回首地址为 begin,超尾地址为 end 的有序数组中,第一个 > num 值的地址int* upper_bound(int *begin, int *end, int num) {int low = -1;int high = end - begin;int mid;while(high - low > 1) {mid = (low + high) / 2;if(begin[mid] > num) {high = mid;} else {low = mid;}}return begin + high;}const int maxn = 100;int n = 10;int a[maxn] = {2, 3, 3, 4, 6, 6, 6, 7, 8, 9};for (int i = 0; i <= 10; ++i) {printf("lower_bound %2d at %2d\n", i, lower_bound(a, a + n, i) - a);printf("upper_bound %2d at %2d\n", i, upper_bound(a, a + n, i) - a);}
并查集是通过修改祖先节点,判断两个节点的祖先是否相同,来判断两个点是否在同一个连通块的数据结构,其时间复杂度为 。除了初始化 Init,并查集有两种操作:询问祖先 Find 与合并两个节点到同一个连通块中 unit。
Init:将每个点的父节点定义为它自己,fa[i] = i;Find:递归查找节点 的父节点,直到找到某个点 的父节点 Fa[x'] = x',则节点 的祖先节点即 ;unit:将节点 与节点 合并到同一个联通块中,分别递归查找节点 与节点 的祖先节点 与 ,将 赋值给 ,使 成为 的父节点。代码:
const int maxn = 10000 + 100;int fa[maxn];void Init() {for (int i = 0; i < maxn; ++i) {fa[i] = i;}}int Find(int x) {return x == fa[x]? x: fa[x] = Find(fa[x]);}void unit(int x, int y) {fa[Find(x)] = Find(y);}