标签(空格分隔): 算法报告 图论 最短路
这是一个耿直的,但是最容易写的代码。
本质是dp
对于只使用前 k 个点,求得最优解
那么就可以dp出 使用前 k + 1 个点的最优解
以下是dp过程
for(int k=1; k<=n; k++)
for(int i=1; i<=n; i++)
for(int j=1; j<=n; j++)
if(e[i][j] > e[i][k] + e[k][j])
e[i][j] = e[i][k] + e[k][j];
Bellman-ford是 不断松弛 寻得最优的过程
每次对所有边松弛操作一次
可以证明,至多 v-1 次,即可求得解
for(int i=1; i<=v-1; i++)
for(int j=1; j<=e; j++){
int u = edge[j].u;
int v = edge[j].v;
int weight = edge[j].weight;
if(dis[v] > dis[u] + weight)
dis[v] = dis[u] + weight;
}
// 判断是否存在负环
bool flag = 0;
for(int i = 1; i<=e; i++){
if(dis[edge[i].v] > dis[edge[i].u] + edge[i].weight){
flag = 1;
break;
}
}
// flag == 1 存在负环
Dij是每次通过获得最快能到达点,在以此继续贪心,来得到最短路的算法。
对于如下 n = 3 的邻接矩阵
0 | 2 | 3 |
---|---|---|
2 | 0 | -2 |
3 | -2 | 0 |
在更新的过程中 Dis[i]的状态如下
步骤 | dis1 | dis2 | dis[3] | 过程 |
---|---|---|---|---|
0 | 0 | ∞ | ∞ | 从点1触发 |
1 | 0 | 2 | 3 | 从点2触发 |
2 | 0 | 2 | 1 | 完毕 |
那么,dis2通过Dij得出的结果是 2
但是,很明显,可以通过 1->3->2的方式得到一个 1 的更优解
为什么Dij会失败呢?
Dij 是贪心,每次贪心得出(最快能到达的点)
负权会产生影响,把之前得到的某个点再次优化
但是这个被影响的点已经跑完了,就不会更新他所能影响的点
Dij的算法步骤如下
步骤① 会重复执行 v 次
步骤② 执行的总次数实际上所有的边 即 e 次
而步骤 ① 需要 获得最优点,朴素的搜索需要 v 次
步骤② 的更新朴素实现是 O(1) 的
所以总时间是 v∗v+e∗1
Dij的Heap优化在于,把获得最优点和更新其他点用堆来实现
获得最优点即是删除堆顶 复杂度为 logV
更新其他点则是修改堆的节点值 复杂度为 logV
所以 Heap 优化后的Dij,时间为 v∗logv+e∗logv
即复杂度为 O(elogv)
实际上,当 e=v2 时 Dij的Heap会退化成一个 O(v2logv)
效率会低于朴素的Dij的实现( O(v2) )
PS:
鉴于 Dij 的实际操作是 降权 和 删除堆顶
使用斐波那契堆 可以让降权操作的摊还复杂度为 O(1)
斐波那契堆实现的Dij的复杂度为 O(e+vlogv)
然而实际上并没有哪个需要用它,毕竟卡SPFA的不会卡heap,而SPFA大法可以解决大部分问题……
Shortest Path Faster Algorithm
吐槽:一般的算法名都是由作者名字命名,然而这个命名则是中文的,尤其是这个Faster,显得很是中二……
这是一个最坏能达到 O(v∗e) 的算法
其为 Bellman-ford 的优化
因为 Bellman-ford 是一个不断松弛的算法,但松弛过程中有很多无效操作
SPFA则把每次被更新的点重新纳入计算,以此减少无效步骤
SPFA的具体做法是维护一个顶点的队列
不断地从头到尾扫描队列
如果队列中某个点的标记值为true
则以此出发更新其他点,被更新到更优解的点,标记true
该点标记false
初始只有起点为true
SPFA 是一种 XJBS 出奇迹的典型……
虽然它的最劣很劣,但通常情况是快于甚至远远快于其他算法的
——有不靠谱的书认为SFPA是一个 O(ke) 的算法(k为常数)
举个SPFA最劣情况的例子
n = 5 的邻接矩阵
矩阵 | 点1 | 点2 | 点3 | 点4 | 点5 |
---|---|---|---|---|---|
点1 | 0 | 16 | 16 | 16 | 1 |
点2 | 16 | 0 | 1 | 4 | 8 |
点3 | 16 | 1 | 0 | 1 | 8 |
点4 | 16 | 4 | 1 | 0 | 1 |
点5 | 1 | 8 | 8 | 1 | 0 |
第一遍遍历队列的过程
点1 会把 2 3 4 5全部更新
此时情况是
Dis | 点1 | 点2 | 点3 | 点4 | 点5 |
---|---|---|---|---|---|
距离 | 0 | 16 | 16 | 16 | 1 |
(之后的过程中,2,3,4点的更新是无效的)
而到达5后,会更新为
Dis | 点1 | 点2 | 点3 | 点4 | 点5 |
---|---|---|---|---|---|
距离 | 0 | 9 | 9 | 2 | 1 |
点2、3、4会重新
下一次,会继续对2、3、4点更新
之后点4又再次更了2、3
这种情况下,会更新V轮
每轮为E次更新
则达到最坏复杂度
DFS和BFS都是 地毯式 搜索
A* 是搜索的时候,带一个 估价函数 试图往更好地方向上搜
记 S 为源点
记 T 为汇点
对于点
我们记录 F(a,b) 为从 a 到 b 的 实际 最短路程
我们记录 P(a,b) 为从 a 到 b 的 估价 最短路
BFS 是 每次求得最小的一个 F(S,a) 的过程
那么,如果我对当前搜得的点,
估算一个 F(S,a)+P(a,T) 如果 P(a,T)=F(a,T)
他实际就是 S 经由 a 到达 T 的最短路
但是 这个 P(a,T) 我无法直接求得
可以证明
如果 P(a,b)<F(a,b) 将可以求得最短路
如果 P(a,b)=F(a,b) 将等效于 BFS
如果 P(a,b)>F(a,b) 将较快搜得结果,但不一定是最短路
Floyd是可以在 V3 内实现求得所有最短结点对的
如果对每个点使用一次SPFA,可以求得结果,但效率不会很稳定。
(鉴于SPFA的最坏是 V∗E ,但实际上似乎对每个点搜索,不会都达到最劣)
如果对每个点,跑一次 Dij
其复杂度为 v3
对每个点跑一次Dij的heap优化
如果使用斐波那契堆,可以达到单次搜索 vlogv
整体复杂度 v∗vlogv
而Dij对负权无能为力
Johnson就是一个对含负权的图作出处理
而后用Dij堆优化来跑得结果的算法
神奇的SPFA,是会被偶尔卡一下的
而堆优化的dij,仍然会死于负权
Johnson就是一个处理带负权的图,让它能跑dij堆来处理所有结点对最短距离的方法。
如果,我们在每个节点,赋予一个权值 h[i]
记原i、j两点的边权为 w[i][j]
新的边权 记为 W[i][j]=w[i][j]+h[i]−h[j]
所寻得的所有最短路,是否与原来一致?
我们记
path[i] 为 原来从源点到 i 点 实际最短路
Path[i] 为 后来从源点到 i 点 实际最短路
我们知道
Path[i]=W[1][v1]+W[v1][v2]+...+W[vn][i]
而 W[i][j]=w[i][j]+h[i]−h[j]
所以
Path[i]=w[1][v1]+w[v1][v2]+...+w[vn][i]+h[1]−h[v1]+h[v1]−h[v2]+...+h[vn]−h[i]
Path[i]=w[1][v1]+w[v1][v2]+...+w[vn][i]+h[1]−h[i]
h[1]−h[i] 是固定的
所以,当 w[1][v1]+w[v1][v2]+...+w[vn][i] 取最优时
Path[i] 取最优
而 path[i] 即是最优选项
所以,合理构造出 h[i] 就可以构造出一张无负权的图
这个合理,可以是
新建一个点 T0 这个点到所有点,连一条权值为0的单向边。
首先对这个点跑一次最短路 SPFA 或 其他
然后把跑出来的值,赋值给每个点
因为 跑完后另 h[i]=d[i]
( d[i] 为从 T0 到 i 的实际最短路)
而 d[j]≤d[i]+w[i][j]
我们要使得 W[i][j]=w[i][j]+h[i]−h[j]≥0 的目的 也就实现了
PS:跑出负环说明原图有负环
最短路是一个实用性很强,应用面很广的算法。
一个经典的问题
人、狼、羊、白菜 要过河
每次渡河 需要人来
但是人不在的时候
狼要吃羊
羊要啃白菜
如何用最少的步骤,安全渡河,没有谁受伤害……
*如果人渡河要花力气,如运白菜要1力气,运羊要2力气什么的,怎样最省力呢?
如果不止有人狼羊白菜,还有喜羊羊灰太狼灰太狼呢?*
我是邪恶的分割线
我们把所有情况罗列
如 人 羊 表示 人和羊在此岸,狼和白菜在彼岸
把每种情况看做一个节点
然后,把不能存在的情况剔除(狼爱上羊,好白菜被羊拱了什么的)
那么,每次人渡河后,会从某情况,变成另一种情况
于是在两种情况间加边(权值为1)
于是问题转变为了
找 从 人狼羊白菜 到 (空) 的最短路
一个 M∗N 的网络,每个节点有一个高度
有一个初始速度 S
每次可以上下左右移动
移动花费的时间是1/S
每次移动后,速度会变化成 S∗2H1−H2
(H1、H2 分别是两点的高度)
求从左上到右下的最短时间
我是邪恶的分割线
2H1−H2∗2H2−H3=2H1−H3
完
双向图,无负权
每条边有长度L和花费K两个值
你有一定的钱数
用给定的钱数,走一条最短路。
N≤100
L,K≤10000
我是邪恶的分割线
很朴素的做法是
用dij的heap做,
每次只要到某点的花费 ≤ K,就把他放进heap里
同时,仍然是把距离最短的作为堆顶
这样弹弹弹,第一个弹出的目的点,必然是距离最短且花费符合的。
World Finals 2009
给出n个字符串后缀替换规则,每个由长度L不超过20的两个等长字符串A,B表示,表示若当前字符串以A为后缀,可以将其替换为B。现给出等长串S和T,问能否替换,最少操作多少次?
例,给出规则如下
1.A - B
2.AB - BA
3.AA - CC
4.CC - BB
S : AA
T : BB
可以 AA - AB(1) - BA(2) - BB(1)
也可以 AA - CC(3) - BB(4)
n <= 100
我是邪恶的分割线
首先,如果搜索是会GG的,因为状态很多,会炸。
我们假设,给出的所有规则,长度一样
那么,是不是就变得和之前的题目0一样,可以连边确定最短路
同时,长度为 L 的状态涉及的变化,是与大于L的长度的状态无关的
再看题目,对于给定长度L
我们必然是 从长度L的初始态,经过某些变化,经过了某些长度L的中间态(也可能没有)(PS:这里的中间态,指的是题目中给出的规则中出现的状态),变为了长度L的最终态
且,两种长度L的相邻的状态(存在于给定规则中),其变化必然不涉及其他的给定中间态。只涉及长度 <L 的状态。
而两种
所以,只需求出任意两种长度L的状态之间的最短变化距离即可
那么,依次递归,求出某长度下状态间的最短路。
说的有点乱,举个栗子
对于 AAAA - DDDD
如果是按下面的顺序变完
AAA - BBB
BB - CC
BCC - BCD
ABCD - PDDP
DDP - DDD
PDDD - DDDD
这样的规则
对于长度为 4 的串 实际中出现了很多
但 我们要求的只是 AAAA ABCD PDDP PDDD DDDD 之间的距离
在假定递归完成的情况的
我们知道了 所有出现在规则中的长度相同的串之间的最短距离
首先 从 A*** 转化到 P*** 这样的转化,必然是需要一个“出现在规则中的”长度为4的串
而在A*** 和 A***之间的转化会涉及到长度小于4的串
这个时候,已经与 A 无关,纯粹的是 * 与 * 之间的转化
而 * 和 * 的出现次数有有限的,就是规则中出现的。
所以
1、把所有的长度为 1 的规则 计算 转化距离
如 A - B A - K (因为 n = 100, 所以最多 200 点)
2、对于长度为2的所有出现的规则(包括后缀)
如果有直接变化规则,加一条权值为1的边
否则,如果是A* - A*的形式,加 * - *的最短路(已经求出)
求最短路
3、对于长度为3的所有出现的规则
如果有直接变化规则,加一条权值为1的边
否则,如果是A** - A** 的形式,求 * - * 的最短路即可
(AA* - AA* 的形式,包含在内)
结点对距离直接用Floyd的话,复杂度为 O(L∗v3) ,可以接受
如果用SPFA的话会更快。
一个 N∗M 的网格图,且每个格子的左上与右下相连,如下
边有权重
小动物从左上向右下逃亡,问最少多少人手可以阻止小动物成功逃亡。
n,m≤1000
权值 ≤106
PS:不许跑网络流
我是邪恶的分割线
套路之平面图最小割
首先这是一个平面图
在图论中,平面图是可以画在平面上并且使得不同的边可以互不交叠的图。而如果一个图无论怎样都无法画在平面上,并使得不同的边互不交叠,那么这样的图不是平面图,或者称为非平面图。
建立其对偶图
假设G=
n只牛排队,从1到n,按编号顺序
同时有一些限制:
1、x 牛 y 牛 距离不能超过 maxd
2、x 牛 y 牛 距离不得小于 mind
求方案使得牛排的队最长
我是邪恶的分割线
套路之差分约束系统
转化为最短路做
有两种情况
不能超过 dy≤dx+maxd
不得小于 dx≤dy−mind
分别是添加单向边
x 向 y maxd
y 向 x - mind
还有默认的 dx≤d(x+1)+0
x+1 向 x 添加 0 的单向边
然后跑一发 1 到 n 的最短路
为什么这样就能解决这个问题呢?
我们看最短路的一个描述
d[v]≤d[u]+w[u,v]
d[i] 表示 源点到 i 点 的 实际最短路
w[i,j] 表示 i 到 j 的距离
最短路是不是就是这玩意儿?
那么,符合这描述的,是不是就是最短路?
好神奇也……
参考内容
1.百度百科
2.https://wenku.baidu.com/view/6b90bc59ad02de80d4d840e6.html
3.ACM国际大学生程序设计竞赛 题目与解读(俞勇主编)