返回首页

算法笔记1.0

6371 字 32 分钟
算法笔记1.0
目录
内容时效性提醒

"本篇博客完成日期距现在已大于一年,内容可能已过时,相关技术、政策或情况可能已发生变化,建议读者查证最新信息。"

左神课程笔记#

前置基本问题:#

1. 归并分治算法#

大范围的答案 等不等于 左边部分 + 右边部分 + 跨越左右两边的答案#

💡考虑跨左右 有序是否能提升便捷性。

  • 归并排序:

💡归并排序是一个稳定的排序。

分成左右,merge排序

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
int a[N];
int help[N];
void merge(int l,int r){
int i = l, j = ((l + r) >> 1) + 1, t1 = 0;
while(i <= ((l + r)>> 1) && j <= r){
help[t1++] = (a[i] <= a[j]) ? a[i++] : a[j++];}
while(i <= ((l + r)>> 1) ){. help[t1++] = a[i++]; }
while(j <= r){. help[t1++] = a[j++]; }
for(i = r; i >=l; i--){. a[i] = help[--t1]; }
}
void guibin(int l, int r,int n){
if(l >= r) return ;
guibin(l, (l + r) >> 1, n);
guibin(((l + r) >> 1)+1, r, n);
merge(l, r);
}
  • 归并分治

💡

归并分治是基于归并排序,在归并排序的基础上进行分 左右 + 左右中的过渡,主要是分析左右中的过渡过程是否跟左右部分的有序性相关。

2. 随机快速排序#

基本内容与快速排序保持一致,只是在选择pivot的时候是随机选择。

构建前缀信息(46)#

常见构建

  • 构建前缀信息 (最早 最晚) 出现的位置
  1. 前缀和来 求区间和
  • sum[i] = sum[i-1] + a[i]

  • {l , r} → sum[r + 1] - sum[l] 2. 求 区间累加和 为确定值的 最长长度(子数组个数)

  • 记录 t = sum[i] - aim 的最早出现次数(i 之前的 t 的出现个数)3. 正数 和 负数 相等的 最长数组长度

  • 正数相当于 1 负数相当于 -1 求区间为 0 1的子数组最长长度

  1. 区间大于 0 的最长数组长度 (值只有 -1, 1)
  • aim = sum[i] - sum[j] ≥ 0
  • if sum[i] > 0 → ans = i
  • if sum[i] ≤ 0 → sum[i] -1 最早出现的位置
  1. 移除最短的数组子数组长度 sum 能被 p 整数
  • 与 余数相关
  • sum1 % p = a // sum2 % p = b
  • if(a + b % p == 0) (sum1 + sum2) % p == 0
  • 整体 aim = sum(总) % p → 看哪个区间的 的余数 (t + aim) % p == 0
  • find = (t + p - aim) % p (同余原理) → 与环也有关

💡

单调队列 和 单调栈 一样,保存着对于答案的可能性,并且从栈中弹出的时候,进行计算,不进行后续的计算,一般应用于 区间问题。

核心思想 : 越往后的 满足要求更好的选择 更好

单调队列#

单调队列基本用法 → 用来维护一个窗口里面的最值(左闭右开)

单调栈#

  • 基本使用方法 维护 左右侧 比 当前元素 大或者小 的最近位置

滑动窗口 + 双指针(视频)#

数据结构#

前缀树(字典树)#

单调栈 + 单调队列#

stack st 便是单调栈的形式,只是栈中的元素是单调的。

priority_queue q 便是单调队列(优先队列)的形式,在优先队列中的元素是单调的。

并查集(模版)#

const int N = 1e5 + 10;
int father[N];
// 初始化并查集
void build(int n) {
for (int i = 0; i < n; i++) {
father[i] = i;
}
}
// 查找元素的根,并进行路径压缩
int find(int i) {
if (i != father[i]) {
father[i] = find(father[i]);
}
return father[i];
}
// 判断两个元素是否属于同一个集合
bool isSameSet(int x, int y) {
return find(x) == find(y);
}
// 合并两个集合
void unite(int x, int y) {
father[find(x)] = find(y);
}

基本建图方法#

  • vector 数组建图
vector<pair<int,int>>a[10005];
  • 链式前向星

拓扑排序#

倍增算法 + ST表(用于区间查询最值,gcd)(先看 基础dp )#

树上问题#

树上倍增 + LCA#

  1. tarjian算法
void tarjan(int u, int f){
vis[u] = true;
for(int e = head[u]; e != 0; e = ed[e].next){
int v = ed[e].to;
if(v != f){
tarjan(v, u);
father[v] = u;
}
}
for(int e = q_head[u]; e != 0; e = que[e].next){
int v = que[e].to;
if(vis[v]){
ans[que[e].w] = find(v);
}
}
}
  1. ST表

树的重心(有一个或者两个)#

树的重心的基本定义:

  • 最大子树的节点数 足够小
  • 每棵子树的节点数 不超过 总节点数的一半
  • 所有节点 汇聚到 重心的 步数最少

补充性质:

  • 一棵树最多有两个重心,两个重心一定相邻
  • 如果树上增加或者删除一个叶节点,重心最多移动一条边
  • 将两棵树连起来,新树的重心一定在两个原来重心的连线上
  • 如果边权为正,所有节点走向重心的 总距离和 最小
  1. 求法一 : 最大子树 足够小
int ans = 0, best = INT_MAX;
/*
重心:
以当前节点为 重心,所有子树中 最大数量的子树的 数量足够小
*/
int dfs(int u, int f){
Size[u] = 1;
int mx = 0;
for(int v = head[u]; v != 0; v = edge[v].next){
int e = edge[v].to;
if(e != f){
dfs(e,u);
Size[u] += Size[e];
mx = max(mx, Size[e]);
}
}
mx = max(mx, n - Size[u]);
if(mx < best || (mx == best && u < ans)){
ans = u;
mx = best;
}
}
  1. 求法二 :每棵子树的节点数 不超过总节点的一半
int Size[N];
vector<int> ans;
void dfs(int u, int f){
Size[u] = 1;
int Mx = 0;
for(int e = head[u]; e != 0; e =edge[e].next){
int v = edge[e].to;
if(v != f){
dfs(v,u);
Size[u] += Size[v];
Mx = max(Size[v], Mx);
}
}
Mx = max(Mx, n - Size[u]);
if(Mx <= n / 2){
ans.push_back(u);
}
}

扩展: 带 点权的树 如何求重心

仅 修改一个 → Size[u] 的初始值 变成了 点权重 56分以上

树的直径#

树上的最长路径

  • 两次 DFS(仅使用没有 负边权)

树上差分#

  1. 点差分
  1. 边差分

树状数组(视频)#

树状数组 是 处理区间查询 的方法。

  • 一般处理 可差分信息 (总体 是 由部分构成的)| 下标一定从 1 开始
  • 怎么得到 最右边的 1 → i & -i

常见有以下四种查询类型

  • 单点增加 + 范围查询

管理范围 (去除最右边的 1( lowbit(i) ) + 1, 自己)

线段树#

基本线段树

动态规划(先做题目)#

背包dp (66 - 75)#

区间dp#

将大范围 划分为 若干个 小范围 的问题

状态dp#

利用 二进制 的 0 1 来表示 节点 状态

树型dp#

将 子树的 信息 返回给父亲

数位dp#

判断 数字的 可能性

换根dp#

将 根节点 互换,要求值的变化

轮廓线dp#

三进制状压dp#

dp优化#

字符串#

KMP#

前缀函数

Manacher#

AC自动机#

字符串哈希#

数学#

埃式筛#

乘法逆元#

逆元含义:

x1xx\rightarrow \frac{1}{x}

法一 :扩展欧几里得 求逆元

typedef long long LL;
LL ExGCD(LL a, LL mod, LL &x, LL &y){
if(mod == 0){
x = 1; y = 0;
return a;
}
LL d = ExGCD(mod, a % mod, x, y), t = x;
x = y; y = t - a / mod * x;
return d;
}
int ExGcdInv(int a, int mod){
LL x, y;
ExGCD(a, mod, x, y);
return (x + mod) % mod;
}

法二 : 快速幂 求逆元

LL fastpow(int a, int b, int mod){
LL ret = 1;
while(b){
if(b & 1) ret = ret * a % mod;
a = a * a % mod;
b >>= 1;
}
return ret;
}
LL FermatInv(int a, int mod){
return fastpow(a, mod - 2, mod);
}

法三 : 费马小递推 求逆元

inv[i]=(mod(mod÷i))×inv[mod%i]%mod\text{inv}[i] = ( \text{mod} - (\text{mod} \div i) ) \times \text{inv}[\text{mod} \% i] \% \text{mod}
int invList[mod+ 10];
voidGetInv(int mod)
{
invList[1]= 1;
for(int i= 2; i< mod; i++)
invList[i]= 1LL* (mod- mod/ i)* invList[mod% i]% mod;
}

容斥原理#

奇 ➕ 偶 ➖ 两个集合:

AB=A+BAB|A \cup B| = |A| + |B| - |A \cap B|

三个集合:

ABC=A+B+CABACBC+ABC|A \cup B \cup C| = |A| + |B| + |C| - |A \cap B| - |A \cap C| - |B \cap C| + |A \cap B \cap C|

n个集合:

i=1nAi=k=1n(1)k+11i1<i2<<iknAi1Ai2Aik\quad \left| \bigcup_{i=1}^{n} A_i \right| = \sum_{k=1}^{n} (-1)^{k+1} \sum_{1 \leq i_1 < i_2 < \dots < i_k \leq n} \left| A_{i_1} \cap A_{i_2} \cap \dots \cap A_{i_k} \right|

相关题目:

  • 计算区间 [1,n] 内不被给定质数整除的整数个数

快速幂#

  • 基本快速幂
typedef long long LL;
const int mod = 1e9 + 7;
LL fastpow(int a, int b){
LL ant = 1;
while(b){
if(b & 1){
ant = ant * a %mod;
}
a = a * a %mod;
b >>= 1;
}
return ant % mod;
}
  • 矩阵快速幂

评论