算法总结

算法总结

第三讲 搜索与图论

DFS

1.使用枚举+递归的方法进行操作

BFS

  1. 队列+枚举的方法进行操作
  2. 发散式一层一层的寻找能够走的点,加入队列中
  3. 并且使用一个另外的数组存储走过的步数
  4. 返回即可

树与图的深度优先遍历

1.适用场景:边无权值图,递归处理子结点(需要获取到子结点的某些状态时
2. 方法:
  ①每次查找子树的结点个数
  ②再反过来 父结点连接的结点数 = 总结点数 - 子树总结点数
  ③返回以当前结点为根节点的树 的总结点数

树与图的广度优先遍历

1.适用场景:边无权值图,求最短路(可以上下左右行走的情况,不往回走即可)
2. 方法:分层的更新

拓扑排序

适用于:无负环的有向图
时间复杂度:O(n)
方法:
① 所有的结点均有入度
② 入度为0时,加入队列中
③枚举所有结点获取入度为0的结点,加入队列中(初始化)
④ 队列不为空时
      1.读取队头元素
      2.用当前的队头元素更新与当前元素相连且未被处理过的结点的入度
         (相当于此时图内删除队头结点)
      3. 如果更新后的结点入度为零,则加入队列中

Dijkstra求最短路

适用于:无负权边且有权重的图\存在自环和重边
朴素做法
  ① 枚举n次 (长度为n的路径)
  ② 找到每次的最短边结点 ,将其记录为已处理O(1)
  ③更新与最短结点相连的结点的距离 O(m)
  时间复杂度:O(nm)
堆优化做法
  ① 枚举n次 (长度为n的路径)
  ② 找到每次的最短边结点 ,将其记录为已处理(使用小根堆查询当前最小值
  ③更新与最短结点相连的结点的距离 (使用邻接表处理
  时间复杂度:O(n
logn)
  使用数据类型:优先队列 priority_queue<数据类型,数据容器,排列方式>

bellman-ford求最短路 (单源)

有边数限制的最短路
适用于:图中可能存在重边和自环, 边权可能为负数
做法:

  1. 循环n次(路径长度为n)
  2. 备份(每次更新的时候是在上一次更新的路径基础上更新)
  3. 枚举每一条边(a,b,w) ,用a的距离更新b的距离
    时间复杂度 O(n*m)

spfa求最短路(单源)

bellman的优化
邻接表+BFS队列
适用于:图中可能存在重边和自环, 边权可能为负数。
想法: 只有被更新过的点,其后续的点到起点的距离才会发生改变
做法:
1.用队列存储更新过的点
2. 当队列不为空时
       ①使用被更新过的点 t 去更新相连的其他结点
       ②将后续结点 m 加入队列、并且设置 m 状态为正在更新
       ③ t 对其他结点的更新完成,将其设置为未在更新
时间复杂度为O(n*m)

spfa判断负环(单源)

邻接表+BFS队列
想法:
如果要是不存在负环的话,
每次 d [ j ] = d [ t ]+w [ t ][ j ] 更新后,该路径上的结点就增加1
所以一条经过图上所有结点的路径的结点最大为 n
if 路径上到达某个结点的结点数 > n,就意味着这条路径上出现负环

思路:
1.用队列存储更新过的点
2. 当队列不为空时
       ①使用被更新过的点 t 去更新相连的其他结点
       ②如果后续结点需要更新,就将到达m路径的结点数+1;
         (如果大于n,直接返回存在负环
       ③将后续结点 m 加入队列、并且设置 m 状态为正在更新
       ④ t 对其他结点的更新完成,将其设置为未在更新
       ⑤返回不存在负环

时间复杂度为O(n*m)

Floyd算法(多源)

适用于:图中可能存在重边和自环,边权可能为负数。 不存在负权回路
想法:
  循环中间结点 k
      循环开始结点 i
          循环终点结点 j
                  d[i,j] = min(d[i,k]+d[k,j])
解读 :对于每个i,j而言,相当于从i,j经历了k条边;而且每次循环并更新一次k,就给 i --> j 的路径内加了一个结点,所以循环k,相当于给所有的结点找个长度为n到达某个点的路径。

Prim 算法

适用于:图中可能存在重边和自环,边权可能为负数而且边数比较少时。求最小生成树
朴素想法:
每次找到距离集合最短的点,用它更新剩下所有点到集合的距离
做法:

  1. 循环n次,保证能够将所有结点收到集合内,一次操作一个结点
  2. 循环所有的结点,找到没在集合内,且距离集合最小的点
  3. 用该点更新剩下不在集合内的所有点
  4. 重复2 、3步,直到所有点均在集合内

Kruskal 算法

适用于:图中可能存在重边和自环,边权可能为负数,,而且边数比较多时。求最小生成树
朴素想法:
枚举边,每次都是添加的最小的边。并查集
做法:

  1. 将所有边按照权重从小到达排序
  2. 枚举每条边 a,b,c
         if(a,b 不连通)
           将这条边加入到集合内,就是说将两个结点放到同一个集合内

染色法判定二分图

适用于:图中可能存在重边和自环,图中不含有奇数环
过程:

  1. 枚举每个结点i,如果没有染色就将他染为1(此时为同一层的结点,第一层) dfs(i,1)
  2. 先将此结点染成目标颜色
  3. 循环子结点相连接的后续结点,
    ①.如果没有染色,就将其染成另外一种颜色(此时可能子结点染色失败)
    ②如果已经染色了,且与染的颜色发生冲突,就失败
  4. 处理结束时,都没有失败,则染色成功

匈牙利算法 二分图最大匹配

想法:
强势找女朋友
先给每个男生找,再对应的女生的已经好上的对象找
做法:
1.如果右侧女生没有对象,就直接匹配成功
2.如果右侧女生有对象,且她的对象能够找到下家,匹配成功
3.如果右侧女生有对象,且她的对象无法找到下架,匹配失败

第四讲 数学

快速幂

想法:
1.将数字 x^k 中的 k 转为二进制存储
在这里插入图片描述
在这里插入图片描述

扩展欧几里得

想法:
//题解成立的前提下
        ax + by     = gcd(a,b)
        by+(a%b)y=gcd(b,a%b)
因为    gcd(a,b) = gcd(b,a%b)
所以 ax +by = bx2+(a%b)y2
                    = bx2 +(a- ⌊a/b⌋)y2
                    = ay2 + b ( x2 - a/b * y2 )
所以算得 x=y2 ; y = ( x2 - a/b * y2 )

又因为 ① exgcd(a,b,x,y) 和 ② exgcd(b , a%b , y , x)
所以当取得②时,y = x2 ; x = y2
由因为等式替换后 ① 的 y = ( x2 - a/b * y2 )
所以此时 y = y - a/b * x = ( x2 - a/b * y2 )
               x不需要进行操作 已经等于y2

中国剩余定理

组合数Ⅰ(取模)

适用于: 数字较小的情况,p一定
时间复杂度: O(n)
原因: 如果数字较大的话,需要开的C数组也会很大,超出范围
公式:C(n,m)=C(n,n-m)=C(n-1,m-1)+C(n-1,m)
方法:递推+数组预处理

组合数Ⅱ(取模)

适用于: 数字较大,同时查询多(此时Ⅰ利用数组的方式已经不再适用)
公式:在这里插入图片描述
C(a,b) = fact[a] * infact[b] * infact[a-b]

方法:使用快速幂求逆元,预处理阶乘

  1. infact:存储逆元的阶乘 O(logn)
    infact[i] = infact[i-1] * qmi(i,mod-2,mod)%mod;
  2. fact:存储阶乘 O(1)
  3. 所以从前往后枚举,计算阶乘 O(n)

时间复杂度 : 1*3 --> O(nlogn)
注意:如果这个算式中出现除法,我们就需要逆元了,将除法运算转换为乘法运算。
参考文章:
https://www.cnblogs.com/czc1999/p/11682068.html

组合数 III(取模)

适用于 p为素数且可变时,数字大,查询少
方法: 将公式化简后结果。
在这里插入图片描述

公式
C(a,b) = C( a%p , b%p ) * C( a/p , b/p ) %p

做法:

  1. 化简约分公式
  2. 计算C(a , b ) O(nlogn)
    仍然使用快速幂求倒数
    使用lucas算法可以减少 能被p 取模的数字的计算量
  3. 查询 O(n)

时间复杂度: O(n2logn)

组合数 IV

组合数不取模,结果会很大
想法
a! = a*(a-1)(a-2)…*1
在这里插入图片描述

目标:取得a!内 p的指数

p的指数 = 1-a内所有数的质因子p的指数相加,所以需要获取所有数的质因子p的指数。
每一次a/p 算的是 a内质因子内包含 p 的个数,也就是a!中p的指数
相当于取出所有数内pk 中一个 p1
在这里插入图片描述
a/p :表示1-a内有多少数含有p因子
每次循环将a!阶乘结果取出一个p
a!
1~a 内的数= k1 * p2 , k2* p2 , k3 * p3 , k4 * p3 * … kn*pk
a/p处理以后
1~a 内的数= k1*p , k2*p , k3 * p2 , k4 * p2 * … kn*pk-1

k=a/p就是将①内1-a所有的数抽出一个p,p的个数k      res=res+k;
k=a/p2就是将①内1-a所有的数抽出一个p2,p2的个数k      res=res+k;

每一层操作永远只在1-a内取 p1 直到取到k次为止,将1-a 的 pk取完。

容斥原理

适用于:需要在集合内选出一些数,计算可以选的方案数时在这里插入图片描述
计算p1 ^ p2 ^ p3 的个数 = C(3,1) - C(3,2) +C(3,3)

总结:
在某个集合中并集 = C(k,1)-C(k,2)+C(k,3)-C(k,4)+…C(k,k)

与、异或运算

1.与运算(&)
运算规则:0&0=0; 0&1=0; 1&0=0; 1&1=1;
即:两位同时为“1”,结果才为“1”,否则为0

2.或运算(|)
运算规则:0|0=0; 0|1=1; 1|0=1; 1|1=1;
  即 :参加运算的两个对象只要有一个为1,其值为1。

3.异或运算(^)
运算规则:0 ^ 0=0; 0 ^ 1=1; 1 ^ 0=1; 1^1=0;
  即:参加运算的两个对象,如果两个相应位为“异”(值不同),则该位结果为1,否则为0。
4. 因为二进制除了最后一位,其他位都是2的幂次方,必然是为偶数的,那么我们可以通过最后一位为0或者1来判断
x^1 =1 奇数
x^1 =0 偶数

博弈论

Nim 游戏 (线性)

拿取一定数目的物品,判断是否必胜
先手必胜状态:先手操作完,可以走到某一个必败状态
先手必败状态:先手操作完,走不到任何一个必败状态
先手必败状态:a1 ^ a2 ^ a3 ^ … ^an = 0
先手必胜状态:a1 ^ a2 ^ a3 ^ … ^an ≠ 0

证明
a1 ^ a2 ^ a3 ^ ai… ^an = x ≠ 0
拿走 ai - (ai ^ x) 后转化为 :
a1 ^ a2 ^ a3 ^ ai ^ x … ^an
= a1 ^ a2 ^ a3 ^ ai … ^an ^ x
=x ^ x
=0

所以:如果一开始是 !0 的状态一定能够走到 0 的状态
           且最后一步为0状态的人走,所以当异或为0极为必败状态

集合Nim

定义:
SG(终点) = 0
SG(x) =mex(SG(y1),SG(y2),…SG(yk))
SG(x) : 不能到达的最小自然数

必胜与必败状态
SG(x) = 0 必败
SG(x) ≠ 0 必胜

集合:只能抽取集合内数目的数字进行操作

sg函数

  1. 如果已经计算过的状态就直接返回 fx //记忆话搜索
  2. 每次枚举能够走到的状态(枚举可以操作的集合)
  3. for循环找到无法到达的整数最小值fx (从0开始)
  4. 如果找到就返回当前的状态fx
    在这里插入图片描述

拆分Nim

适用于:将一个状态分为两个或者多个状态,求必胜与必败结果
sg函数二维使用
sg(a,b) = sg(a) ^ sg(b)
想法
一堆的sg(a)转化为两堆的sg(b,c)
做法

  1. 如果计算过状态,则直接返回结果 fx;
  2. 枚举所有的分法,得到多个方案,计算每个方案的结果,为sg(a)可以到达的状态(sg(i,j) 为了避免重复计算,i<j )
  3. for循环找到不能到达的最小自然整数 fx;
  4. 返回状态的结果fx;
  5. 总的:
    所有堆开头的sg()异或结果 ≠ 0 即必胜
    否则 = 0 必败

第五讲 动态规划

01背包问题

核心:选与不选
二维数组+动规
状态转移方程:
定义f[i][j]:前i个物品,背包容量j下的最优解

1)当前背包容量不够(j < w[i]),为前i-1个物品最优解:f[i][j] = f[i-1][j]
2)当前背包容量够,判断选与不选第i个物品
选:f[i][j] = f[i-1][j-w[i]] + v[i]
不选:f[i][j] = f[i-1][j]

完全背包问题

核心:每个物品可以无限选择,选与选k个
定义: f[i][j]:前i个物品,背包容量j下的最优解
状态转换公式: 可以错位相消,减少纬度
f[i][j] = max(f[i-1][j] , f[i-1][ j-v[i] ]+w[i],f[i-1][j-2 * v[i] ]+2w[i],…,f[i-1][j-k * v[i]]+k * w[i]
f[i][j-v[i]] = max(f[i-1][j]-v[i] , f[i-1][ j-2
v[i] ]+w[i],f[i-1][j-3 * v[i] ]+2*w[i],…,f[i-1][j-(k+1) * v[i]]+k * w[i]

f[i][j] = max(f[i-1][j] ,f[i][j-v[i]]+w[i])

多重背包问题

核心:可以不选或者选择k个物品,但是有上限Si
朴素想法
可以不选或者选择k个物品,三重for循环解决
二维优化:
想法:
由于公式
f[i][j]=max(f[i][j],f[i-1][j-kv[i]]+kw[i]);
需要使用到上一层 i-1 的数据,所以体积从大到小处理,再更新需要使用未更新的体积较小的数据,即i-1层数据
二进制优化
想法:
将Si物品用二进制表示,分别当作新的物品,
2k *v[i]的体积,2k *w[i] 价值

分组背包

核心:每个分组只能选一个物品
朴素想法:

  1. for三重循环,分组,体积,每个分组内的物品
  2. i分组内,不选或者选择第k个物品
    公式:
    f[i][j]=max(f[i-1][j],f[i-1][j-v[i][k]]+w[i][k]);

二维优化:
体积由大到小处理
f[j]=max(f[j],f[j-v[i][k]]+w[i][k]);

线性DP

数字三角形

闫氏分析
f[i,j]:从起点到达 ( i , j ) 数字和的所有方案
属性: max
状态转移:
1.从左上角到达 (i,j) 的方案 f[i-1][j-1]
2.从右上角到达 (i,j) 的方案 f[i-1][j]
转移公式: f[i][j] =max(f[i-1][j-1],f[i-1][j])

最长上升子序列

闫氏分析
f[i]:前i个数字中以 a[i]结尾 的上升子序列所有方案
属性: max
状态转移

  1. 1 ~ j 内找到比a[i]小的方案+1,找到最大值
  2. 默认当前以 a[i]结尾的上升子序列的长度为1
    转移公式: f[i] =max(f[i],f[ j ]+1)

优化:
适用于:数据较多时
时间复杂度: O(nlogn)
做法:

  1. 循环数组内每个数字 O(n)
  2. 使用二分法获取到最右的<x的数 O(logn)
    在这里插入图片描述

最长公共子序列

闫氏分析法:
f[i,j] : 字符串 A 的 1-i 与 字符串 B的 1-j 匹配的公共子序列
属性: max
状态转换:

  1. 最长公共子序列 包含 Ai , Bi ,此时Ai=Bi  11
    f(i-1,j-1)+1
  2. 最长公共子序列包含 Ai , 不包含 Bi         10
    f(i,j-1)
  3. 最长公共子序列不包含 Ai , 包含 Bi         01
    f(i-1,j)
  4. 最长公共子序列不包含 Ai , 不包含 Bi     00
    f(i-1,j-1) 此项被包含于 2、3 项处理完毕,不需要再冗余的处理

区间DP

将区间划分为子区间,然后枚举划分的方式,找到最值
属性:min/max

计数类DP

子结点所有的可能性相加 为 父结点的 结果
属性:count

f[i,j] 前i个整数拼成j 的方案数
属性: count
状态转移
不选i / 选k个i 的情况 —>完全背包问题
f[i][j] = f[i - 1][j] + f[i - 1][j - i] + f[i - 1][j - 2 * i] + …;
f[i][j - i] = f[i - 1][j - i] + f[i - 1][j - 2 * i] + …;
错位相消后:
f[i][j]=f[i−1][j]+f[i][j−i];

注意:
如果总和为0, 不选也算是一种方案

数位统计DP

想法:

  1. 可以将所有的数字抽象为 abcdefg
  2. abcdefg
  3. 1~ abcdefg 查看某个数字出现的位置
    可以表达为 xxxdxxx
  4. count(n,x) 1-n中x出现的次数
  5. 某个区间内x出现的次数 = count ( R , x ) - count ( L -1 , x )
  6. 枚举x在 每一位中出现的次数

过程:

  1. 1~n, x=1;

  2. n = abcdefg

  3. 求 x 在每一位出现的次数

  4. eg:
     求 x 在第四位出现的次数(求1-n中出现的次数,所以需要保证xxx1yyy < = abcdefg )

    xxx1yyy
    ①如果 xxx选择0~abc-1
        xxx<abc ,则后面的yyy = 0~999 ,所以方案数:abc*1000
    ② 如果 xxx = abc
        ②.1 如果d=1
                yyy = 0~yyy-1
                此时方案数为:yyy
        ②.2 d>1
                yyy= 0~999
                方案数为: 1000
        ②.3 d<1 则没有方案数

特殊情况:

  1. 如果查找的x = 0 时,
          情况①,因为前导0的问题,只能选择 1 ~ abc-1
  2. 如果x放于第一位a时,不用考虑情况①
  3. 如果x放于最后一位g时,不同考虑情况②,情况①已经包含了所有方案数。

状态压缩DP === 图案连通性

  1. 将前一列的放置作为一种状态
  2. 用二进制表示放置的状态
  3. 如果前一列中出现连续的空位不是偶数个,即可认为失败
  4. 当前i列的状态需要与前一列j的放置状态一起占据当前列的位置,剩余的空格不为偶数,也认为失败

做法:
f[i,j] 第i列,放置的状态为j的所有情况

  1. 预处理所有状态中,k中所有连续的空位均为偶数的状态。 st[x] = true;
  2. for 每一列
  3. for 此一列的状态j
  4. for 前一列的状态k
    ①如果k状态与j状态一起占据的i列的位置后,剩余的空位出现不是为偶数的连续空位,则失败 st[j|k] == true;
    ② i-1层伸出来k的与当前i列需要放置j的位置不能冲突 j&k == 0

状态压缩DP === 集合式

思想:由前一个状态走到当前状态,且距离最短
用二进制表示所有点的选择情况

树状DP

想法:
每个位置的状态有哪些+不同状态之间如何互相转换
树状DP的体现: DFS

闫氏分析
状态表示:
f[i,0]: 第i个物品不选择
f[i,1]:第i个物品选择
属性:最大值

状态转化:
f[i,0]:此时子结点可以选择 选/不选 max(f[j,0],f[j,1]) 相加
f[i,1]:此时子结点选 f[j,01]相加 + h[i]

记忆化搜索

想法:
对于不会出现改变的结果,可以采用记忆化搜索的方式存储
有效减少时间消耗

对于每一个点而言,往下走的路径是一定的,不会受到外界影响,所以用记忆化搜索

区间合并

左右取法:
1.如果是需要尽量多的重合,则取右排序

区间选点

将每个区间按右端点从小到大排序
从前往后枚举每个区间

  1. 如果当前区间中包含点,则直接pass
  2. 否则,选额当前区间的右端点
区间最大不相交区间数量

理解:将整个区间 选 几个集合(最多) 互不相交
想法: (想法可能会错误)

  1. 如果选择左排序,可能会导致,每次都是用了最长的区间占据了整个区间
  2. 如果使用右排序,因为每次都是尽量多的产生并集,所以相对的每个区间会短一点

做法:
如果互相有交集的区间,就分到一个部分去

  1. 将所有区间按照右端点从小到大排序
  2. 从前往后枚举每个区间
    ①如果当前区间已经包含点,则直接pass
    ②否则,选择当前区间的右端点。
区间分组

想法:

  1. 选择左排序,需要不相交的结果最大,所以用左排序
  2. 选择右排序,相交的结果最大,所以不适用

判断存不存在组的条件:

  1. 一组区间,如果下一个区间左端<当前组的最大右端点,则表示有交集,不能合并,需要新的分组
  2. 否则,就合并在一起,并且更新最右侧的点
  3. 优先队列存储每个分组的右端点,则每次都比较最小右端点。
    ①如果某个区间左端点<=最小的右端点,说明该区间与所有区间有交点,需要新开分组
    ② 如果> 最小右端点,表示至少和当前的最小右端点的分组不冲突,可以合并,更新当前最小右端点。

做法:

  1. 将所有区间按照左端点从小到大排序
  2. 从前往后枚举区间
  3. 判断是否将其放入当前现有的组内
    ①如果不存在这样的组,则开新组,让后将其放进去
    ②如果存在这样的组,就将其放进去,并且更新当前组的Max_r
区间覆盖

在这里插入图片描述
想法:

  1. 将所有区间按照右端点从小往大排序
  2. 枚举每个区间,
    ①每次选择 左端点<=当前区间的右端点,且右端点最大的区间
    ②更新当前区间的右端点

Huffman树

想法:
Huffman树:
每次合并所有选择中最小的两个。
总和为最小

排序不等式

第一个人等待:
0
第二个人等待:
t1
第三个人等待:
t1+t2
.
.
.

第n个人等待:
t1+t2+…+tn-1

总的等待时间:
t1*(n-1)+t2(n-2)+…tn-1
所以应该让等待时间从小到达排列

绝对值不等式

在这里插入图片描述
如果x取在ab外,则x到a和b的距离为:
ax+bx
显然,如果x取在ab内,到a和b的距离和为:
ab
所以x取在ab内为最小值 = ab的中点

推公式

耍杂技的牛
假设:已经排好奶牛的顺序
颠倒一下 i 奶牛 和 i-1 奶牛

  • 第 i-1 奶牛的风险值:
    allW[i-2] - Si-1
  • 第 i 奶牛的风险值:
    allW[i-2] + Wi-1 - Si

颠倒后:

  • 第 i-1 奶牛的风险值:
    allW[i-2] +Wi - Si-1
  • 第 i 奶牛的风险值:
    allW[i-2] - Si

化简:

  • 第 i-1 奶牛的风险值:
  • Si-1
  • 第 i 奶牛的风险值:
    Wi-1 - Si

颠倒后的化简:

  • 第 i-1 奶牛的风险值:
    Wi - Si-1
  • 第 i 奶牛的风险值:
  • Si

因为:
Wi-1 - Si > - Si
Wi - Si-1 > - Si-1
所以i奶牛和 i-1 奶牛的最大值为:

  • 交换前最大值:Wi-1 - Si
  • 交换后最大值:Wi - Si-1

如果让交换过的结果中每个的风险值 < 原排序
Wi-1 - Si > Wi - Si-1
Wi-1 +Si-1 > Wi + Si

所以所有大小从小到大排序,最大值最小

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值