目录
网络流
线性规划
回溯算法
分支限界
贪心算法
动态规划
分治算法
算法复杂度分析
相关概念
网络流
下面是本章需要掌握的知识
• 流量⽹络的相关概念
• 最⼤流的概念
• 最⼩割集合的概念
• Dinic有效算法的步骤
• 会⼿推⼀个流量⽹络的最⼤流
下面对此依次进行复习
首先看流量网络的相关概念
上面是课程PPT中的定义,真是抽象
实际上,我们直接将某个流量网络进行建模出来就是上图这样,其中每一条弧上的第一个数字代表该条弧的最大流量,第二个数字代表该条弧的实际通过流量,并且定义一个发点V1、收点V6,其余的就是中间点了
那么上图所说的可行流又是什么?
实际上可行流即从发点到收点的流量都是合法的
- 即每一条弧的实际流量f>=0且f<=c
- 并且每个中间点的接收流量之和=发出流量之和
- 发点发出流量之和=收点接收流量之和
下面看前向弧、后向弧的概念
假设有一条链u,它链接了发点与收点,且定义链的方向是从发点到收点(注意链的方向与弧的方向无关)
那么链上的弧分为了
下面看增广链的概念
假设有一条从发点到收点的链,当该链满足如下条件时为增广链
- 如果链上的某条弧是前向弧,则必须满足0<=f<c,即是非饱和弧
- 如果链上的某条弧是后向弧,则必须满足0<f<=c,即是非零流弧
下面看割集的相关概念
割量也称为割集的容量
还是看一个例子
如上图所示,我们在流量网络上将发点和收点分开了
那么现在的割集为(v1,v2),(v1,v3)
现在的割量为5+6=11
这是一个割集与一个割量,但是我们将发点与收点分开的方式有多种,将它们都求出来,找到最小割量,就是找到最小割集了
定理:最小割量的大小就等于网络的最大流量
下面是Dinic的算法步骤
1. 从0流𝑓开始
2. 构造分层辅助网络
3. 如果𝐴𝑁 𝑓 存在s-t最短路径
① 求得𝐴𝑁 𝑓 上的极大流𝑔
② 叠加流𝑓 = 𝑓 + 𝑔;
GOTO 2
4. 否则, 𝑓为所求最大流
注意这里的分层网络是为了方便我们判断是否某条弧是否还有残余流量
并且在增广链的构建过程中则会判断是否找到的顶点的层数是当前顶点的层数+1(即还有残余流量)
下面演示标号法求一个流量网络的最大流
首先给发点初始化为(0,正无穷)
如上图所示,我们找到一条增广链V1 V3 V4 V7
接着对当前增广链上找到一个最小流量即3,之后对链上的弧都加上该流量即可
然后将点上的标号都取消,重复上述步骤
如上图所示,我们再次找到一条增广链,注意这次的增广链中存在一个后向弧,注意后向弧的标号与前向弧的标号是不同的
发现链上最小流量为1,那么链上的前向弧流量+1,后向弧流量-1
再次重复上述步骤
这次我们发现不管怎样,我们都没办法使得增广链到达收点
因此我们就已经得到了最大流量为f12+f13=7+13=20
不难发现最小割集的容量即6+3+3+8=20,也印证了最小割量=最大流量的结论
总结
线性规划
重点掌握以下内容(针对min类型的)
- 将数学模型化为标准型
- 手写单纯形法
- 化为对偶线性规划
先看如何化成标准型
例题
首先将目标函数变为min型,其中变量的系数也要变为相反数
再看约束条件中右边是否存在<0的数,如果有也需要将该式子化为正的
接着补充松弛变量和剩余变量,即对于<=的式子我们需要在该式子的左边+松弛变量
对于>=的式子我们需要在式子的左边-剩余变量,记得在最下面补充上松弛变量与剩余变量都>=0的条件
最后将自由变量x3在式子中的位置都替换为x3'-x3''
同样的需要在最下面补充x3'>=0与x3''>=0
接下来看如何使用单纯形法
看一个例子
首先先化为标准型
画出单纯型表的大致模样
取第0行的一个<0的列作为换入变量,接着在这一列中,计算出每一行的检验数,即第0列的每一行去除刚刚选出的那一列的每一个数,注意当其中存在<=0的数时得到的检验书无效
然后得到最小的检验数的那一行作为换出变量
此时我们确定了一个位置的点,我们需要利用矩阵的行变换将除了选出的那一行外其余全部变为0
接着重复上述过程
直到第0行全部为>=0时则确定我们得到了最优解
下面是判断解的情况的方法
下面看如何化为对偶线性规划
首先我们需要清楚的是对偶线性规划有什么用
我们前面所学的线性规划只能用于解决约束条件都为<=的情况
但是实际应用中,我们不可能保证约束条件都为<=,而对偶线性规划就补充了这点
因为其可以允许约束条件的右边存在<0的数
那么我们就还是可以保证约束条件都<=的情况了
对偶的性质:如果原始规划(P)有最优解, 则对偶规划(D)也有最优解, 且它们的最 优值相等。反之亦然
还是看一个例子
首先原先是max对偶就是min,反之类似的
然后原问题目标函数的系数对应着对偶问题约束条件的右边
原问题约束条件的右边对应着对偶问题的目标函数系数
原问题变量的符号对应着对偶问题约束条件的符号(即原问题x1x2x3都>=0,因此对偶问题三个约束条件都是>=的)
原问题约束条件的符号对应着对偶问题的变量符号(即原问题约束条件都是<=的,因此对偶问题的4个变量都是>=0的!注意这是相反的)
此外还需要注意 如果存在任意与等于的情况,那么任意的相反是等于,等于的相反是任意
例题
回溯算法
本章需要掌握的内容如下:
- N皇后问题
- 0-1背包问题
- 装载问题
- 图着色问题
首先介绍N皇后问题
首先我们认为每个皇后会被放在第i行,其次我们需要搜索的是每个需要被放在第i行的哪一列上
因此我们先构建一个数组sol[]用于存储下标为i的皇后被放在了sol[i]列上,接着使用DFS,搜索每个皇后是否能够被放在第i列上,判断条件是是否当前的位置(i,j)与sol数组内的皇后在同一行/在同一列/在同一条45度斜线上
具体代码实现如下
接着看背包问题
有 n 种物品,每种物品只有 1 个. 第 i 种物品价值为 vi , 重量为 wi , i =1, 2, … , n. 问如何选择放⼊背包的物品,使得总重量不超过 B, ⽽价值达到最⼤?
首先对问题进行建模
那么搜索的过程中,我们只需对每一个物品遍历0或1,即遍历是否该物品要选择
补充搜索空间的概念,这里每一个物品代表了搜索树中的一个层数,而每个物品都有两种选择即选或不选,那么每个节点就有两个分支,那么搜索树的叶子共有2^n(其中n为物品数)
下面是装载问题
我们假设第一艘船的装入量为W1(W1<=c1)
那么我们使用DFS搜索使得c1-W1最小的装载方案
那么如果所有集装箱重量之和-W1<=c2,则找到一种解决方案,反之找不到
该问题的算法实现实际上与01背包类似的,这里不再给出代码
下面是一个例子
下面是图着色问题
对问题建模
搜索空间为n^m
具体代码实现如下
分支限界
实际上,上面所讲的回溯问题,对于NP问题,搜索空间会指数级增长,而这里的分支限界就是对回溯问题的一种优化,提高回溯的效率(剪枝)
下面是两个分支限界的概念
根据上述概念
我们可以确定停止当前分支进行回溯的依据
- 不满足约束条件了
- 对于最大化问题,代价函数的值小于当前的界
贪心算法
本章需要掌握的有:
• 活动选择问题
• 最优装载问题
• 最⼩延迟调度问题
• 找零钱问题
一般来说,使用贪心算法之前都会对数据进行排序,再配合约束条件进行决策
- 如果贪心策略可以得到最优解,需进行正确性证明
- 反之,需要举反例
首先看活动选择问题
决策过程是多步判断,每步依据某种“短视”的策略进⾏活动选择,选择时注意满 ⾜相容性条件
对于上述三种策略我们需要一一检验
对策略1而言
很明显如果一个最早开始的活动持续时间很长,那么期间所有活动都被阻塞了
对于策略2
如果一个活动时间很短但是它刚好位于两个活动之间,那么它就阻塞了两个活动
对策略3,是正确的
证明正确性这里忽略
最优装载问题
该问题比较简单,容易想到,每次选择最小的集装箱装载即可
最小延迟调度问题
正确的策略是按照Deadline的大小从小到大进行调度
找零钱问题
这个比较容易想到每次使用面值最大的钱进行支付即可
动态规划
本章节需要掌握的有:
• 矩阵链相乘问题
• 投资问题
• 背包问题
• 最⼤⼦数组和
动态规划算法的使⽤必须保证问题满⾜以下性质:
⼀个最优决策序列的任何⼦序列本⾝⼀定是相对于⼦序列的初始和结束状态的 最优决策序列
即问题的子问题的结构一定是与原问题结构相同的
动态规划的设计步骤
• 1. 刻画⼀个最优解的结构特征(参数、维度等);
• 2. 递归地定义最优解的值(递推⽅程);
• 3. 采⽤⾃底向上/带备忘的⾃顶向下办法计算最优解的值(⼀个标量);
• 4. 利⽤计算过程中的信息构建⼀个最优解(⼀个选择序列)。 • ** 如果仅需要计算最优解的值,可以忽略第4步。如果确实需要做第4步,应在 求解过程中维护⼀个或多个备忘录,采⽤回溯的办法构造最优解。
先看矩阵链相乘问题
这里主要是需要我们学会手写完成动态规划表
例如上图中的m[1,2]=30*35*15,其余的以此类推
m[1,3]=min{15750+30*15*5,2625+30*35*5},其余的以此类推
投资问题
注意这里的m万元钱为整数分配
首先对问题进行建模
接着定义子问题
得到递推方程
当k=1,即只有一个项目时,那么对于的F(x)也就等于投入钱数x之后的收益直接查表即可
解释上图计算的过程F2(2)即计算对于前两个项目,拥有资金为2万元时的最大收益,那么即等于max{当前项目投入0元+前面的项目投入2-0元的收益,当前项目投1万元+前面项目投入2-1万元的收益,当前项目投入2万元+前面项目偷人2-2万元的收益}
其余的依次类推即可
同样的这里要学会填表
背包问题
对问题进行建模
定义子问题为更小的背包容量以及更少的物品种类
建立递推方程
如果每种物品存在多个的话
最大子数组和
定义子问题为左边界为1,右边界为i的数组的最大子数组和是多少
接着寻找子问题之间的关系
不难发现
如果opt(i-1)<0,即opt(i)+A[i]<A[i],那么opt[i]=A[i]
反之,即opt(i)+A[i]>=A[i],那么opt(i)=opt(i-1)+A[i]
分治算法
本章需要掌握的要点如下
• ⼆分检索
• 归并排序
• 幂乘算法的应⽤
• 改进分治算法的两个⽅法
• 增加预处理
• 减少⼦问题个数(整数位乘、矩阵乘法。只需知道算法原理和复杂度)
首先认识分治算法
二分查找
该算法比较简单直接给出代码
#define _CRT_SECURE_NO_WARNINGS #include<cstdio> using namespace std; const int MAX = 1e7+10; int n, m; long long arr[MAX]; int bin_search(int q) { int l = 1, r = n, mid=0; while(l<r){//终止时l=r mid=(l+r)/2; if(arr[mid]>=q){ r=mid; }else{ l=mid+1; } } return l; } int main() { scanf("%d%d", &n, &m); for (int i = 1; i <= n; i++) { scanf("%lld", &arr[i]); } while (m--) { long long q; scanf("%lld", &q); long long ans=bin_search(q); printf("%lld\n", ans); } return 0; }
归并排序
幂乘问题
这也是快速幂的思路来源
给出代码
#include<iostream> using namespace std; int t,a,b,p; int main(){ cin>>t; while(t--){ int res=1; cin>>a>>b>>p; //求a的b次方 int base=a; int index=b; while(index>0){ if(index%2==1){//指数为奇数 //将奇数变偶数 index--; res=res*base%p;//抽离出多余的一个底数 }else{//指数为偶数 index/=2;//指数缩小一半 //底数变为原来的平方 base=base*base%p; } } printf("%d\n",res); } return 0; }
改进分支算法的两个方法
预处理即在算法实验题整数位乘中的补零操作
减小子问题个数即 整数位乘中我们不一定在小规模问题使用分治,即小规模问题使用正常解法即可
算法复杂度分析
相关概念
对于相同输入规模的不同实例,算法的基本运算次数也不⼀样,可定义两种时间复杂度
• 最坏情况下的时间复杂度W(n)
• 算法求解输入规模为n 的实例所需要的最⻓时间
• 平均情况下的时间复杂度A(n)
• 在给定同样规模为n 的输入实例的概率分布下,算法求解这些实例所需要的 平均时间
其中A(n)的计算公式如下
即使用概率论中的均值计算公式
下面是上述两种时间复杂度的计算例子
函数的渐近表示
下面介绍递推方程
首先认识递推方程
下面介绍两种求解递推方程的方法
1、迭代法
步骤如下
例题
补充知识
2、递归树
例题
主定理
例1
观察可知a=9,b=3,因此logba=2,因此
又f(n)=n,即f(n)=o(n^2)
符合上述主定理的第一种情况
因此递推方程
例2
因此递推方程为