DP入门——DAG上的动态规划

Posted GGBeng

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了DP入门——DAG上的动态规划相关的知识,希望对你有一定的参考价值。

有向无环图(DAG,Directed Acyclic Graph)上的动态规划是学习动态规划的基础。很多问题都可以转化为DAG上的最长路、最短路或路径计数问题。

一、DAG模型

【嵌套矩形问题】

问题:有n个矩形,每个矩形可以用两个整数a、b描述,表示它的长和宽。矩形X(a , b)可以嵌套在矩形Y(c , d)中当且仅当a<c,b<d,或者b<c,a<d(相当于把矩形X旋转90°)。例如(1,5)可以嵌套在(6, 2)内,但不能嵌套在(3, 4)内。你的任务是选出尽可能多的矩形排成一行,使得除了最后一个之外,每个矩形都可以嵌套在下一个矩形内。如果有多解,矩形编号的字典序应尽量小。

分析:矩形之间的“可嵌套”关系是一个典型的二元关系,二元关系可以用图来建模。如果矩形X可以嵌套在矩形Y里,就从X到Y连一条有向边。这个有向图必然是无环的,因为一个矩形无法直接或间接地嵌套在自己内部。换句话说,它是一个DAG。这样,所要求的便是DAG上的最长路径。

【硬币问题】

问题:有n种硬币,面值分别为V1, V2, ..., Vn,每种都有无限多。给定非负整数S,可以选用多少个硬币,使得面值之和恰好为S?输出硬币数目的最小值和最大值。1 <= n <= 100, 0 <= S <= 10000, 1 <= Vi <= S。

分析:此问题尽管看上去和嵌套矩形问题很不一样,但本题的本质也是DAG上的路径问题。将每种面值看作一个点,表示“还需要凑足的面值”,则初始状态为S,目标状态为0。若当前在状态 i,每使用一个硬币 j,状态便转移到i - Vj 。

补充:这个模型和上一题类似,但也有一些明显地不同之处:上题并没有确定路径的起点和终点(可以把任意矩形放在第一个和最后一个),而本题的起点必须为S,终点必须为0 。点固定之后“最短路”才是有意义的。在上题中,最短序列显然是空(如果不允许空,就是单个矩形,不管怎样都是平凡的),而本题的最短路径却不容易确定。

二、最长路及其字典序(嵌套矩形)

  如何求DAG中不固定起点的最长路径呢?

  仿照数字三角形的做法,设d(i)表示从结点 i 出发的最长路长度,应该如何写状态转移方程呢?第一步只能走到它的相邻点,因此:d(i)= max{d(j)+1} ,(i , j)∈E,E为边集。则最终答案是所有d(i)中的最大值。

  有条理地列下来就是:

  • 状态:d(i)
  • 状态转移方程:d(i)= max{d(j)+1} 

  我们可以使用递推或记忆化搜索的方法计算状态转移方程。不管使用哪种方法,我们都要先把图建立出来,假设用邻接矩阵保存在矩阵G中(一定要确保建图过程正确无误)。

  我们在此以记忆化搜索的方法求解,下面给出记忆化搜索程序(调用前需初始化d数组的所有值为0):

int dp(int i)
{
	int& ans = d[i];			//为表项d[i]声明一个引用ans 
	if(ans > 0)	return ans;
	ans = 1;
	for(int j=1;j<n;j++){
		if(G[i][j]){
			ans = max(ans,dp(j)+1);
		}
	}
	return ans;
}

  这里使用了一个技巧——为表项d[i]声明一个引用ans。这样,任何对ans的读写实际上都是在对d[i]进行。当d[i]换成d[i][j][k][l][m][n]这样很长的名字时,该技巧的优势就会很明显。

  提示:在记忆化搜索中,可以为正在处理的表项声明一个引用,简化对它的读写操作。

  原题还有一个要求:如果有多个最优解,矩形编号的字典序应最小。

  我们可以将所有d值计算出来以后,选择最大d[i]所对应的i。如果有多个i,则选择最小的i,这样才能保证字典序最小。接下来可以选择d(i)= d(j)+1 且(i , j)∈ E的任何一个 j 。为了让方案的字典序最小,应选择其中最小的 j 。程序如下:

void print_ans(int i){
	printf("%d ",i);
	for(int j=1;j<=n;j++){
		if(G[i][j] && d[i]==d[j]+1){
			print_ans(j);
			break;
		}
	}
}

  提示:根据各个状态的指标值可以依次确定各个最优方案,从而构造出完整方案。由于决策是依次确定的,所以很容易按照字典序打印出所有方案。

  注意,当找到一个满足d[i]= d[j]+1的结点j后就应立刻递归打印从j开始的路径,并在递归返回后退出循环。如果要打印出所有方案,只把break语句删除是不够的。正确的方法是记录路径上的所有点,在递归结束时才一次性输出整条路径。程序可以自行编写。

  有趣的是,如果把状态定义成“d(i)表示以结点i为终点的最长路径长度”,也能顺利求出最优值,却难以打印出字典序最小的方案。想一下,为什么?

三、固定终点的最长路和最短路——硬币问题

 

以上是关于DP入门——DAG上的动态规划的主要内容,如果未能解决你的问题,请参考以下文章

DP入门——线性结构上的动态规划

The Tower of Babylon UVA - 437 DAG上的动态规划

DAG上的动态规划之嵌套矩形问题

UVa 103 Stacking Boxes --- DAG上的动态规划

Codeforces 919D Substring ( 拓扑排序 && DAG上的DP )

动态规划入门(dp)