二分与并查集
二分
- 二分可以用来查找元素
- 分为整数二分和小数二分
整数二分:
- 查找精确值
int find_t(int l, int r, int target)
{
while(l <= r){ //结束条件为l + 1 == r;
int mid = l + r >> 1;
if(a[mid] == target) return mid;
if(a[mid] < target) l = mid + 1;
else r = mid - 1;
}
return l;
}
- 查找大于/大于等于target的第一个元素
int find_t(int l, int r, int target)
{
while(l < r){ // 结束条件为 l == r;
int mid = l + r >> 1;
if(a[mid] > target) r = mid;
else l = mid + 1;
}
return l;
}
- 查找小于/小于等于target的第一个元素
int find_t(int l, int r, int target)
{
while(l < r) {
int mid = l + r + 1 >> 1;
if(a[mid] < target) l = mid;
else r = mid - 1;
}
return l;
}
小数二分
double find_t(double l, double r)
{
const double eps = 1e-6;
while(r - 1 > eps){
double mid = l + r >> 1;
if(check(mid)) r = mid;
else l = mid;
}
return l;
}
并查集
一种树形结构,主要解决一些元素分组的问题。
- 合并 : 把两个不相交的集合合并为一个集合。
直接将一个元素的父节点设为另一个元素的父节点即可。
void merge(int i, int j)
{
fa[find(i)] = find(j);
}
- 查询 : 查询两个元素是否在一个集合中。
采用递归实现查询,一步步访问父节点,直到访问到根节点。(查询两个元素是不是在一个集合中就只需要查询两个元素的根节点是否相同即可)
int find_x(int x)
{
if(p[x] != x) p[x] = find_x(p[x]);//路径压缩,把他的节点直接与根相连
return x;
}
前缀和与差分
- 一维前缀和 :
sum[i] = sum[i - 1] + a[i];
- 二维前缀和:(可以理解为矩形的面积)
sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j -1] + a[i][j];
- 一维差分:
void insert(int l, int r, int c)
{
b[l] += c;
b[r + 1] -= c;
}
再对b数组求前缀和即可得到原数组a;
4. 二维矩阵
void insert(int x1, int y1, int x2, int y2, int c)
{
b[x1][y1] += c;
b[x2 + 1][y1] -= c;
b[x1][y2 + 1] -= c;
b[x2 + 1][y2 + 1] += c;
}
求原矩阵
for(int i = 1; i <= n; i ++){
for(int j = 1; j <= m; j ++){
b[i][j] += b[i - 1][j] + b[i][j - 1] - b[i - 1][j - 1];
}
}
双指针
利用两个指针去遍历 数组,链表,字符串, 以达到所需实现的目标。在某些复杂情况下,使用两个指针来操作数据会更为快捷、高效。
- 对撞指针:一般是在有序数组中使用,一个放首,一个放尾,同时向中间遍历,直到两个指针相交,完成遍历。
- 滑动指针: 将嵌套循环问题转化为单层循环问题,降低时间复杂度, 提高效率。它将两个指针的区间范围当作一个滑块,然后将这个窗口在数组上滑动。在窗口滑动的过程中,左边会出一个元素,右边会进一个元素,然后计算对比当前窗口内的元素即可。
for(int i = 0; j = 0; i ++){
while(j < i && check(i, j)) j ++;
}
素数筛
- 埃氏筛法:比较朴素的筛法
void get_primes(int n)
{
for(int i = 2; i <= n; i++){
if(!st[i]){
primes[cnt++] = i;
for(int j = i + i; j <= n; j += i) st[j] = true;
}
}
}
- 欧拉筛法: 核心思想:让每一个合数被其最小质因数筛掉
void get_primes(int n)
{
for(int i = 2; i <= n; i++){
if(!st[i]) primes[cnt++] = i;
for(int j = 0; primes[j] <= n / i; j++){
st[primes[j] * i] = true;
if(i % primes[j] == 0) break;//直到找到i的最小质因子退出循环
}
}
}
快速幂
核心思想:每一步都把指数分成两半,而相应的底数做平方运算。这样不仅能把非常大的指数给不断变小,所需要执行的循环次数也变小,而最后表示的结果却一直不会变。
int qmi(int a, int k, int p){
int res = 1;
while(k){
if(k & 1) res = (LL)res * a % p;
k >>= 1;
a = (LL)a * a % p;
}
return res;
}