动态规划
动态规划(dynamic programming)是运筹学的一个分支,是求解决策过程(decision process)最优化的数学方法。20世纪50年代初美国数学家R.E.Bellman等人在研究多阶段决策过程(multistep decision process)的优化问题时,提出了著名的最优化原理(principle of optimality),把多阶段过程转化为一系列单阶段问题,利用各阶段之间的关系,逐个求解,创立了解决这类过程优化问题的新方法——动态规划。
https://blog.csdn.net/hebtu666/article/category/8018091基础总结
状态压缩
我们在进行动态规划时,有时状态相当复杂,看上去需要很多空间,比如一个数组才能表示一个状态,那么就需要对状态进行某种编码,进行压缩表示。
比如:状态和某个集合有关,集合里可以有一些元素,没有另一些元素,那么就可以用一个整数表示该集合,每个元素对应于一个bit,有该元素,则该bit就是1。
这个皇后问题就可以帮助理解https://blog.csdn.net/hebtu666/article/details/84631083
旅行商问题
旅行商问题(TravelingSalesmanProblem,TSP)是一个经典的组合优化问题。经典的TSP可以描述为:一个商品推销员要去若干个城市推销商品,该推销员从一个城市出发,需要经过所有城市后,回到出发地。应如何选择行进路线,以使总的行程最短。从图论的角度来看,该问题实质是在一个带权完全无向图中,找一个权值最小的Hamilton回路。由于该问题的可行解是所有顶点的全排列,随着顶点数的增加,会产生组合爆炸,它是一个NP完全问题。由于其在交通运输、电路板线路设计以及物流配送等领域内有着广泛的应用,国内外学者对其进行了大量的研究。早期的研究者使用精确算法求解该问题,常用的方法包括:分枝定界法、线性规划法、动态规划法等。但是,随着问题规模的增大,精确算法将变得无能为力,因此,在后来的研究中,国内外学者重点使用近似算法或启发式算法,主要有遗传算法、模拟退火法、蚁群算法、禁忌搜索算法、贪婪算法和神经网络等。
如图,我们如何从0点出发,以最小代价走过所有的点?
分析
抛开这个图,我们试想:我们可能从0点一次性走到哪些点呢?
只可能1点、2点、3点、4点(废话)
那我们如果知道了
1)从1点开始走,经过所有的点,最后走到了0点的最小距离;a
2)从2点开始走,经过所有的点,最后走到了0点的最小距离;b
3)从3点开始走,经过所有的点,最后走到了0点的最小距离;c
4)从4点开始走,经过所有的点,最后走到了0点的最小距离;d
请再次注意:
我们可能从0点一次性走到哪些点呢?
只可能1点、2点、3点、4点(废话)
所以我们可以从0点走到1点,再从1点经过最小距离走到0点。
2点、3点、4点同理。
那么我们取min(a+3,b+MAX,c+4,d+MAX)即可。
3为0点走到1点的距离,4为0点走到3点的距离。
MAX代表此路不通。。
那我们继续思考,abcd怎么求呢?其实同样的:
比如:求a
从1点开始走,经过所有的点,最后走到了0点的最小距离;a
同样的思考:我们可能从1点一次性走到哪些点呢?
答:可以走到0点、2点、3点、4点。
注意:由于问题要求为:只能经过每一个顶点一次,所以我们要去掉0点,因为我们经过0点,最后再到0点就会重复,所以,
“经过所有的点”是不准确的,是除0点的所有的点。
所以只有三个点可能从1点走过去,我们需要求:
1)从2点开始走,经过除0点所有的点,最后走到了0点的最小距离x
2)从3点开始走,经过除0点所有的点,最后走到了0点的最小距离y
3)从4点开始走,经过除0点所有的点,最后走到了0点的最小距离z
然后取min(x+到2点的距离,y+到3点的距离,z+到4点的距离)
归纳表达式
1)我们会发现,每一次状态转移其实对应的是一个点的集合:
以后还会有经过除1,2点,经过除1,2,3点等等。
2)还有从哪个点开始走:
这两个条件就可以描述当前的状态。
我们把上面的总结用公式表示出来:
S为已经访问过的点的集合
v表示当前所在点。
那么dp[S][v]就表示为从点v出发访问剩余点,最终返回0点的最小长度。
我们把刚才的求解过程用公式表达出来。
∉这个符号我实在没找到,理解意思。
压缩
像这样,状态可以根据集合表示的DP,我们称作状态压缩DP。
状态和某个集合有关,集合里可以有一些元素,没有另一些元素,那么就可以用一个整数表示该集合,每个元素对应于一个bit,有该元素,则该bit就是1。
本题来说,所有点都在集合里就是11111
哪个点没在,对应的位就为0
注意:对于状态不是整数而是集合的情况,通常不太容易确定DP的顺序,如果确实不好想顺序,可以通过记忆化搜索来做。
集合S,如果对于任意S(i)<S(j),那么一定有i<j。所以确定了顺序。
确定dp表的大小,有n个城市,从0开始编号,那么dp表的行数就是n,列数就是2^(n-1)
int n;
int d[MAX_N][MAX_N];//邻接矩阵
int dp[1<<MAX_N][MAX_N];
对于数字x,要看它的第i位是不是1,那么可以通过 (x >>i) & 1取出那一位来看
先用足够大的值初始化dp数组,不要影响结果,然后核心代码:
for(int S = (1<<n)-2 ; S >= 0 ; s--)//每一个集合
{for(int v = 0 ; v < n ; v++)//每一个出发点{for(int u = 0 ; u < n ; u++)//访问每一个点{if(!(S>>u&1))//判断是否在集合中{dp[S][v] = min(dp[S][v] , dp[S | 1<<u][u]+d[v][u]);}}}
}
总结
当我们把状态压缩应用到动态规划中,可以用来精简状态,节约空间,也方便转移。
最常见的就是用二进制来表是状态,利用各种位移运算,就可以实现O(1)的转移。