加载中...
【数据结构】莫队
这篇博客起源于本人把一道 $pow(2,n)$ 的问题考虑成求组合数前缀和的问题qwq,于是接触到了这个新算法来总结一下 参考自这篇文章,写得太好了 首先是一道模板题 题目意思是,给出一个数组a,再给出多个区间,问这些区间里分别有多少不一样的数字 焗个栗子:比如给出的是这样一个数组,询问的两个区间是红色和绿色标注的区间 如果我们分别遍历每一个区间,复杂度就太高了,因此就有大佬提出这样一种算法—— 首先定义双指针 $l$、$r$,初始化为 $l=0,r=-1$(表示此时区间内没有任何元素),然后用unorderd_map<int, int> map存储当前区间内每个元素的个数 看我们要求的第一个区间 $[0,5]$ $l$ 此时等于$0$,和所求区间的左端点相同,因此无需移动 $r$ 此时等于$-1$,在所求区间右端点的左侧,因此需要向右移 首先向右移一位变成 $0$,此时区间内添加了一个元素 5,因为map[5] = 0,元素5不存在于原来的区间里,因此元素种类数cnt加一,map[5] ++ 然后 $r$ 再右移一位变成 1,此时区间内添加了一个元素 7,因为map[7] ...
【数据结构】ST表与RMQ算法
本文参考【朝夕的ACM笔记】数据结构-ST表 在练习线段树的过程中经常会感叹代码怎么这么长啊啊啊懒标记怎么这么难传啊啊啊 于是在得知有一种代码量远小于线段树的算法时、、、(其实是因为做到了[SCOI2007] 降雨量 就是ST表啦~ 在什么情况下可以用ST表代替线段树呢? ==不需要区间修改==的==可重复贡献问题== 不需要区间修改很好理解,什么叫做可重复贡献呢? 我们知道,求一个数组的最大值(比如说长度为10的数组),我们可以先求前六个数的最大值,再求后七个数的最大值,最后求这两个最大值的最大值,虽然这中间有重复的元素,但是对最终的最大值结果不会有影响,这就叫做可重复贡献问题。 但是如果我们要求一个数组中所有元素的和(还是比如说长度为10的数组),我们就不能用前六个元素的和加上后七个元素的和了,这就叫做不可重复贡献问题。 常见的可重复贡献问题包括:求区间最大/小值,区间按位和/或,区间gcd… 怎么构建ST表呢? ST表是一种基于倍增算法的数据结构 我们设f[i][j]表示区间 $[i, i + 2^j - 1]$ 的最大值,因此f[i][0]表示的就是第 i 个元素本身了 由倍增 ...
【数据结构】线段树
原理==时间复杂度:O(logn)== 线段树是一棵二叉树,把一段区间分成多个部分类似堆的方式,用一维数组存整棵树 对于编号x的结点: 父结点 $\lfloor x \rfloor$,表示为 x >> 1 左子树 $2x$,表示为 x << 1 右子树 $2x+1$,表示为 x << 1 | 1 对于长度为n的区间,最坏估计有 $4n-1$ 个结点,因此 ==开数组时空间一般开 $4n$== pushup由子结点计算父结点的信息 模板:// u表示当前树中结点编号 lr表示树中结点左右子结点void pushup(Node &u, Node &l, Node &r){ /* 此处用[l]和[r]的值更新[u] */}void pushup(int u){ pushup(tr[u], tr[u << 1], tr[u << 1 | 1]);} build将一段区间初始化为线段树 首先记录下当前区间的左右端点,如果左端点和右端点相等就直接返回 如果不 ...
【数据结构】树状数组
参考:https://zhuanlan.zhihu.com/p/574739597 树状数组主要是支持两种操作: 单点修改 区间查询 这两个操作的时间复杂度都是 O(logn)根据前缀和的原理,任意一段区间求和都可以转换成两个前缀和的差,因此区间求和问题转换成求前缀和问题 前提准备:计算 lowbit在此我们定义一个 lowbit(x) ,表示 x 的最末尾一个 1 与这个 1 后面的所有 0 组成的二进制数lowbit(x) 应该怎么实现呢?很简单,这里直接给出结论 lowbit(x) = x & -x还是通过例子来说明 现有二进制数 101100x = 101100反码:010011补码:010100 可以注意到,-x 是 x 末尾的一个 1 到结束的 0 不动,前面全部取反的结果那么 x & -x 就只保留了末尾的 1 和后面的 0因此得到我们的 lowbit 函数:int lowbit(int x){ return x & -x;} 树状数组的含义我们定义原数组为a,用一个数组 tree 维护若干个小区间,tree[i] 表示 ...
【数据结构】并查集
今天补题遇到了这个知识点,能想到这个方法但是自己没办法实现,所以来复习一下相关知识做个总结~ 并查集,简单来说,合并两个集合,然后能迅速判断两个元素是否在同一集合中(查询某个元素的祖先结点) 时间复杂度可以达到 $O(logn)$ 通过路径压缩和按秩合并的优化,可以使并查集算法达到$O(1)$ 记录每个集合大小,将信息绑定到根结点(其余节点的信息对不对无所谓) 记录每个结点到根结点距离,将信息绑定到每个元素身上 板子返回 x 的祖先结点,同时进行路径压缩,让 p[x] 直接指向祖先结点 int find(int x){ if (p[x] != x) p[x] = find(p[x]); return p[x];} 实现方法:首先所有元素各为一个集合,创建数组 p[i],意为 i 的父结点,起初,p[i] 全部等于 i 当两个元素 a b 进行合并时(实际上就是两个集合进行合并),让 a 的祖先结点的父结点等于 b 的祖先结点,a 的祖先结点直接指向 b 的祖先结点p[find(a)] = find(b); 当询问两个元素 a b 是否在同一个集合 ...
【C++】位运算
因为对位运算实在是太太太太太不熟悉了!所以每次遇到位运算相关的题都要卡好久才能把题目意思转化成容易理解的样子,今晚又被卡了所以一怒之下总结一篇等下次被卡就来翻翻qwq 与 &翻译:同为1取1,只要有0就取0 可以用&来取每一位上的数,如果要判断n的第三位是否为1,就进行 $n$ & $2^{3-1}$ 运算,如果结果为 $2^{3-1}$ ,就说明当前判断的位数上是1,结果是0,就说明当前判断的位数上是0 判断奇偶a & 1 == 1 a为奇数a & 1 == 0 a为偶数 或 | (OR)翻译:有1取1,无1取0 一个数对另一个数进行 | 操作,当前位上是0将不产生任何影响,当前位上是1将会把对应位上变成1 把一个数变成最接近的偶数:a = a | 1 - 1 异或 ^ (XOR)翻译:相同为0,不同为1 一个数和本身进行异或运算得到结果为0 一个数和0进行异或运算得到结果为本身 上两条推出:奇数个相同的数异或运算得到结果为本身,偶数个相同的数异或运算得到结果为0 左移 <<翻译:a << b = a x 2 ...
【基础算法】逆序对
逆序对,简单来说,就是i > j*,但a[i] < a[j],那么a[i] 和 a[j]就是一组逆序对 求逆序对有三种方式—— 暴力 复杂度 O(n^2^) 谁用谁T 不多赘述了 归并排序 复杂度 O(nlogn) 树状数组 复杂度 O(nlogn) 归并排序 首先看一下归并排序的原理,就是将一个序列无限二分,直到每一部分都只有一个元素,这时每一部分都有序,然后逐次合并相邻部分,让合并后的各个部分有序举个栗子现在我们要合并两个部分:1 3 5 7 9 | 2 4 6 8 10先比较 1、2,发现 1 < 2,所以把 1 先放到合并后的数组里现在剩下的两部分是:3 5 7 9 | 2 4 6 8 10现在比较3、2,发现 3 > 2,所以把 2 放到合并后的数组,由于左半部分是有序的,所以 2 小于左半部分剩下的所有数,但 2 又在左半边剩下的所有数后面,所以 2 和这些数都构成逆序对,逆序对的数量就是mid - i每一次右半部分的第一个数小于左半部分的第一个数时,右半部分的第一个数和左半部分剩下的所有数都构成逆序对,因此在原来的基础上加上mid - i即可 ...
【基础算法】前缀和与差分
前缀和一维一维数组中,计算出所有前 n 个数的和,存储在一个单独的数组里,便于后续计算 板子s[0] = 0;for (int i = 1; i <= n; i ++ ){ scanf("%d", &a[i]); s[i] = s[i - 1] + a[i];} s[i] 计算的就是前 i 个数的和当我们需要计算第 l 个数到第 r 个数的和时,只需要用 s[r] - s[l - 1] 即可 二维二维数组中,计算出长为 0 - l, 宽为 0 - r 的矩阵和这个值等于长为 0 - (l - 1) 宽为 0 - r 的矩阵加上 长为 0 - l 宽为 0 - (r - 1) 的矩阵减去 长为 0 - (l - 1) 宽为 0 - (r - 1)的矩阵 最后加上该位置的值 板子for (int i = 1; i <= n; i ++ ){ for (int j = 1; j <= m; j ++ ) { s[i][j] = s[i - 1][j] + s[i][j ...
【基础算法】高精度
当比赛中给定数字位数过多,无法直接进行加减乘除运算时,使用高精度计算 高精度加法高精度加法相当于一个列竖式计算的过程,从最低位开始(因此所有数都要倒着存),遇十进位 板子vector<int> add(vector<int> &A, vector<int> &B){ vector<int> C; // 计算结果 int t = 0; //记录进位 for (int i = 0; i < A.size() || i < B.size(); i ++ ) // 就是一个竖式计算的过程 { if (i < A.size()) t += A[i]; if (i < B.size()) t += B[i]; C.push_back(t % 10); t /= 10; } if (t) C.push_back(1); return C;}int main(){ ...
【基础算法】二分
二分适用于满足二段性的序列,当一个序列中一段满足条件,另一段不满足条件时可以考虑使用二分来加快查找速度 板子// x 是需要查找的数int l = 0, r = n - 1;while (l < r){ int mid = l + r >> 1; if (q[mid] >= x) r = mid; // 符号按需要更改 else l = mid + 1;}return r; 符号判断二分中使用什么符号曾经困扰了我很久,现总结如下:大原则:搞不清就带等号,带等号的时候和字面理解意思相同其中,以下两个式子表示含义相同: >= 会输出满足大于等于条件的第一个数 < 会输出从后往前看不满足小于条件的第一个数 以下两个式子表示含义相同: <= 会输出从后往前看满足小于等于条件的第一个数 > 会输出不满足条件的最后一个数 举个栗子 现有如下序列:1 2 3 4 5 5 5 6 7 8 9 现需查找 “5” —— 当使用 >= 时,找到的是第 1 个 5 当使用 <= 时,找到的是第 3 个 ...