有向无环图拓扑排序_从有向无环图到动态规划(及优化)问题

题外话:(发现原定的周一更新咕了)

此文是用于传授"动态规划"的本质,公司面试题目可能会略低于本篇文章所讲,会提到各类优化算法到底"应该去优化什么"。阅读时间约10分钟。(如滚动数组、各种数据结构/CDQ分治优化等)

问题开始之前,先谈谈一些定义:

有向图

点的集合
边的集合
边的表示形式为三元组的形式
:代表
有一条边,边权为

注意无向图其实是有向图的一种特殊形式,只要建立边

,有向图就是无向图了。

路径:多条边连成的一条长链,

无环:对于任意点对

如果存在一条路径使得
能到达
,那么不允许存在其它路径使得

某个点的入度:进入这个点的边的个数。

简单路径:路径上的顶点都不相同的路径。

有向无环图:有向图+无环条件,简称为

好了,让我们进入正题。

给定一个有向无环图

,一个起点
,一个终点

现在要求

的最短路,
的入度为0。

由于最短路满足性质:如果

的最短路上,那么
的这条路线也是最短路。

这里不要想着任何最短路算法。

让我们直接用简单的代码,描述这个"求

的最短路"的算法:

代表点
的最短距离,这个距离是动态更新的。
void dfs(Point u)
{
    for all edge(u, v, w) in G:
        if dist[v] > dist[u] + w
            dist[v] := dist[u] + w
            dfs(v)
}

可是这样复杂度肯定太高了,因为存在这种情况:

  1. 目前的值是最优解,点
    的"当前最优解"更新了一些点的dist.
  2. 之后,点
    被其它点(在
    之前更新的点)的解所更新,导致
    需要继续更新"之前它更新过的那些点的dist"。
  3. 这造成了冗余,那能不能存在一种更新顺序,使得 用
    去更新其它点的情况下,就已经是最优解呢?(也就是说,在dfs(v)执行时,保证
    dist[v]是从点S出发的最短路的长度

既然说了它是一个有向无环图,不如我们用拓扑排序的做法:每次取出入度为0的点

,取出从它出发的每条边
, 更新最短路的同时,删掉这些边(这会使得点
的入度减小 ),不断操作直到找不出可以删除的边为止。
void 

显而易见地,按照这种顺序,在每次加入一个点

到队列后,可以更新
的那些点对于点
的影响已经施加完毕了,
保证了入队时, dist[v]是从点S出发的最短路的长度。

参考一下这张图片:

92f734e359229bd63ff0e875b663e2a1.png
默认边权为1

考虑一下进入队列的顺序,那么可以是{S,1,2,3,4,T}或者{S,2,1,3,4,T}

任意一种更新最短路的顺序,都能保证当前进入队列的点的dist值为最短路的长度。

比如现在已经做完了{S,1,2},删除掉了从{S,1,2}出发的每条边,图是这样的:

046cd6c7c71585815769baca527cf6b5.png
此时,点3的入度为0,所以点3入队。

由于已经没有能进入到点3的边了,也就意味着"能更新点3的最短路的点已经不存在了"。

此时dist[3]的值,就是最终的值它已经走到了最优解。这和原始dfs代码中能得到的dist[3]的值是完全相同的。

那么继续用3去更新4的最短路。

结论:如果按照一定顺序(拓扑序)对图上每个点进行更新,当这个点去用来更新其它点的最短路时,该点的最短路已经被算出,且为最优解。


也就是说,按照拓扑序进行计算最短路,就可以保证每一步都是最优解,每一步都是正确的。

废话了这么半天,来看题吧。

39227d36db1b5cd792a0025acff7c8ad.png

给定一个序列

,希望你找出一段连续的
,使得这段序列的和最小,求出最小的和。

啥?刚刚不是还在说图嘛?怎么跳到数字上来了?

别急。让我们对着序列傻傻地建立一张有

个点的图:我们把刚刚的有向图的边权设置为
,第
个点的点权设置为
。不妨新建一个虚拟点
,其点权也是

代表从点
出发的最小距离。

建立以下的边

代表连续选择
,从
点引出
条边分别到点
,边权为0。
void Build()
{
    dist[0] = 0
    i = 0
    while(i <= n)
        if i < n then addedge(i, i + 1, 0);
        addedge(0, i, 0);
}

求这张图中,0到所有点

的最短路就对应了选择出来的序列的最小和。

这个图显然是一个有向无环图,也就可以通过上面的拓扑排序+求最短路做。


什么?你讲了这么半天,到底啥是动态规划啊?

"如果一个题可以用图论思想,建图之后用最短/最长路算法去解决,并且这个图是一个有向无环图,那么这个问题就是动态规划。" (Dynamic Programming,简称dp)

但这类题目真的需要用图论方法做嘛?答案是不需要。

只要你设计出合理的"状态",再找出一个合适的更新的"顺序"(使得轮到这个点更新其它的点时,保证这个点是最优解)以及可以更新状态的递推式,就能够跳出图论方法去解这道题。

这道题的主流解法是这样的,令

为以
结尾的序列的最小的和,那么
可以通过这样两种方式得到:

1.通过和

及之前的一些数字组成一个子序列。

2.以自己一个元素结尾。

(其实f[i]就对应之前的dist[i]。)

那么不难写出递推式

这里, 状态就是
代表以
结尾的序列的最小的和
"顺序"就是从1到n,并不是从n到1,也不是其它奇奇怪怪的顺序,这样的顺序递推能保证f[i]是最优解。 递推式呢,就是
了。

讲到这里,其实已经很明确了:

思路如下:

首先设计状态,也就是"xx代表xx",随便举一些例子:"f[i]代表前i个数字的最优解" "g[i][j]代表第i天还剩j元钱的最优解" "h[i][j][k]代表投掷了i次筛子,j次是1,k次小于等于5的概率"

之后不需要设计更新状态的顺序

然后设计递推式:

如果设计好了递推式,比如长成这个样子:

显然从n到1的顺序更新就能直接得到答案。

如果设计出来的递推式有一个奇怪的形式:

.

似乎"没有一个可以入手的点"。脑袋里建出图来看看:这个图形成了一个环!

那这时候一般有两种情况:1. 这个题根本就不是一个动态规划的题 2. 状态设计的不好,导致递推式形成了环。 解决方法很暴力:推翻一开始设计的状态,重新来过。

事实上动态规划的难点就是在于1. 设计出来一个正确的状态 2. 设计一个正确的递推式


(其实练多了就会发现永远有一大票做不出来的动态规划题)

上面这些就是有向无环图与动态规划的联系了,接下来随便做几道题冷静一下:

当然动态规划题目的特征一般就是:

  1. 最优解问题 2. 计数问题

那些网上说的几类"树形dp,区间dp,背包dp,……"都是不同的dp题目,但其实只要设计好状态就可以做,所以你会的dp题目和你做过的dp题目类型(不是数目)有多少并没有任何联系,只和数目有关。

并不是说你做了一道树形dp的题,之后所有树形dp的题就都会了,恰恰相反,如果你只做了一道树形dp的题,那么下次考树形dp一定是不会的。

好了鸡汤就说这么多,让我们看一些题目:

这里有背包问题的讲解​blog.csdn.net

题目1:旅行商简化版

有n个二维平面的点,一个人从西边的某个点出发,到达最东边的某个点,然后返回,但是返回的时候不能经过原来经过的点,所有点(除了起点)都需要走恰好一遍。求最短的距离。 (

解法:

是已经目前到了
的最短距离,这时候忽然发现解决不了两个问题:
  1. 解决不了"检验每个点是否走过"
  2. 检验不了回去怎么不经过同一个点。

这是完全错误的状态设计,一眼就可以排除掉的垃圾状态。

好了,考虑正确的状态:

因为可以把路程拆分成"向东"和"向西"两部分,设

代表从西的部分当前所在的点为
,向东的部分当前所在的点为
时,目前走过的最短距离之和。

设计递推式:

设k = max(i,j) + 1,则有:

f[i][k] = min(f[i][k],f[i][j] + dis[j,k]);

f[k][j] = min(f[k][j],f[i][j] + dis[i,k]);

这当然也是递推式……因为这样做能算出最终答案,也能保证答案是最优的。

题目2:vijos 1243 (摘自我自己的博客)

af77f44d6054d2c30b1b88f066adbc3c.png
传送门​blog.csdn.net

注意这个题目是可以用单调队列优化的,不要忘记。

题目3: BZOJ 3036

Description

随着新版百度空间的下线,Blog宠物绿豆蛙完成了它的使命,去寻找它新的归宿。

给出一个有向无环的连通图,起点为1终点为N,每条边都有一个长度。绿豆蛙从起点出发,走向终点。

到达每一个顶点时,如果有K条离开该点的道路,绿豆蛙可以选择任意一条道路离开该点,并且走向每条路的概率为 1/K 。

现在绿豆蛙想知道,从起点走到终点的所经过的路径总长度期望是多少?

Input

第一行: 两个整数 N, M,代表图中有N个点、M条边

第二行到第 1+M 行: 每行3个整数 a b c,代表从a到b有一条长度为c的有向边

Output

从起点到终点路径总长度的期望值,四舍五入保留两位小数。

--------

题解很简单,考虑状态

代表点
走到终点的期望距离,那么转移方程(递推式)也相对好写一些:

至于顺序的选取,采用记忆化搜索/拓扑排序都可以。


其实还有其他的一些dp题目还在整理……但是就不放上来了,接下来讲讲dp的优化部分。

1.斜率优化,也是似乎大家最不会的一个优化。

其实很简单啦~如果这里听不懂以后还会开专栏的。

记得BJWC2017出现过一个奇妙的题目:

其中

是一个单调增加的数组。

(遇到这类题目,凡是遇到一个j一个i一个平方,往斜率优化的方向猜我就没输过)

展开。

由于

只和
相关,由于枚举了
之后,
也只是一个常量。

所以我们可以简单地化成这样的形式:

。注意一个事情,因为
是只取决于
的,所以可以在算
之前就已经有了
的信息。把
看作一个二维平面上的点,每次计算
的值就转化成了:给定一个固定的斜率
,求
这个斜率在之前每个点上的截距的最大值。

这就是斜率优化。

接下来维护之前的点的凸包,以及在凸包上二分即可。

这里可以考虑用CDQ分治。(这些名词是啥以后会讲到……)

2.各种数据结构优化:

一般很容易就能看出来的。 比如这个:

由于是一个二维偏序的形式,而且由于按照动态规划的顺序是能保证

的,所以其实就只需要保证一个维度即可。

用各种数据结构都能迅速解决偏序问题,最典型的就是平衡树、线段树、树状数组balabala……


总之暂时是告一段落了,题目并不是很多,但本质地说明了有向无环图和动态规划之间的联系,也稍微总结了各种动态规划里面的优化……

但是看懂这篇文章对于学会做题应该没什么帮助,还是要多做题,独立去设计状态及状态的递推式。

ps:微信公众号是同名的,欢迎关注~

我的其它文章:

制糕神的算法工坊:OI/ACM中的哈希表,一些哈希算法以及题目​zhuanlan.zhihu.com
573178fd21f4196005cd32711b50fd95.png

我的近期回答:

八数码为什么可以归结为图的最短路问题?​www.zhihu.com
51a32db019db19d09924275a9d0793de.png

其实这里"数字对图上的点的转化"和这篇文章的思路差不太多。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值