DP综述

来源:互联网 发布:兰州知行学院官网 编辑:程序博客网 时间:2024/05/17 23:35



一.DP的实质:记忆搜索
DP的过程:
1.定义状态:每个状态是一个搜索的过程,所以状态必须能包含所有情况。我们必须非常清楚每一步的状态的准确的含义。
同时,DP具有最优子结构,所以我们应该做到让每个状态都是现在问题的一个子问题。所以如果能找到子问题,那么状态就浮出水面了
2.通过含义构造递推式
比如背包问题dp[i][j]的其实就包含了每个物品取或不取的状态(把前i个物品中的一些放入重量为j的背包)
dp[i-1][j]把前i-1个物品放入重量为j的背包和dp[i-1][j-W]+C把第i个放入背包



二、dp一定要对每一步的状态十分清楚
比如LCS问题,如果简单地定义a[i]为前i个能达到的最长子序列,那么递推关系就找不到。究其原因,是因为我们没有清楚每个状态的准确含义,这样我们就更不清楚我们定义的状态是否能包含所有情况。但是,如果我们定义a[i]为以第i个为结尾的最长公共子序列的长度,那么就可以了。再如IOI2000的post office问题,如果我们定义dp[i][j]为前i个村庄由j个邮局控制,那么也无法找到递推关系,但是如果我们定义dp[i][j]为前i村庄由j个邮局控制,并且第i个村庄一定建立一个邮局,那么这个问题就可以解决了。




三、DP对于排列组合个数,概率期望都很有效,因为dp能对以前的结果进行记录,很容易满足递推关系的要求
例:挑战程序设计竞赛p66 划分数:
首先想法dp[i][j]定义为把i个划分为j份的个数
那么dp[i][j]=sum(dp[i-k][j-m] k从1到i,j从1到m)但是这样复杂度为10^12。但是如果用dp[i][j]=sum{dp[i-k][j-1]+dp[k][1]}(先取出k个组成一份,其它的组成j-1份,那么1+1+2,和1+2+1将被视为不同,因为dp[i-k][j-1]的计算其实也会涉及到这样的划分,这样会改变计算的实质)
但是后面的递推式的理解见http://blog.csdn.net/sentimental_dog/article/details/51942630





DP的实质:记忆化搜索.DP只是一种记忆化的枚举

对于一个完全决策问题,全部搜索是2^n的时间,但是这其中包含了很多重复

朴素的背包算法

int W[maxn];int V[maxn];int N;int res(int curi,int curj){if(curi==N) return 0;//没有剩余物品int result;if(curj<W[curi]) result = res(curi+1,curj)else result=max(res(curi+1,curj),res(curi+1,curj-W[curi])+V[curi]);return result;}

这是2^N的解法,遍历了所有可能的背包

注意理解curi==N的做法,没有物品的时候,是不能增加贡献的。

在这里由于都是相加然后return,单独拿出每一步来看,这个函数res(curi+1)计算的都是以后的,与现在的情况无关。


如果使用记忆化搜索,每次记录当前物品和重量的情况

</pre><pre name="code" class="cpp">int dp[maxn][maxn];memset(dp,0,sizeof(dp));int memory(int curi,int curj){if(!dp[curi][curj]) return dp[curi][curj];int res;if(curi==N) res=0;else if(curj<W[curi]) res=memory(curi+1,curj);else res=max(memory(curi+1,curj),memory(curi+1,curj-W[curi])+V[curi]);return dp[curi][curj]=res;}

如果采用记忆化方式,由于i,j只有N*W种可能,所以O(N*W)




DP实质的理解



当你企图使用计算机解决一个问题是,其实就是在思考如何将这个问题表达成状态(用哪些变量存储哪些数据)以及如何在状态中转移(怎样根据一些变量计算出另一些变量)。所以所谓的空间复杂度就是为了支持你的计算所必需存储的状态最多有多少,所谓时间复杂度就是从初始状态到达最终状态中间需要多少步!


如果一个阶段的最优无法用前一个阶段的最优得到呢?

什么你说只需要之前两个阶段就可以得到当前最优?那跟只用之前一个阶段并没有本质区别。最麻烦的情况在于你需要之前所有的情况才行。

再来一个迷宫的例子。在计算从起点到终点的最短路线时,你不能只保存当前阶段的状态,因为题目要求你最短,所以你必须知道之前走过的所有位置。因为即便你当前再的位置不变,之前的路线不同会影响你的之后走的路线。这时你需要保存的是之前每个阶段所经历的那个状态,根据这些信息才能计算出下一个状态!

每个阶段的状态或许不多,但是每个状态都可以转移到下一阶段的多个状态,所以解的复杂度就是指数的,因此时间复杂度也是指数的。哦哦,刚刚提到的之前的路线会影响到下一步的选择,这个令人不开心的情况就叫做有后效性。


契机就在于后效性。

有一类问题,看似需要之前所有的状态,其实不用。不妨也是拿最长上升子序列的例子来说明为什么他不必需要暴力搜索,进而引出动态规划的思路。

假装我们年幼无知想用搜索去寻找最长上升子序列。怎么搜索呢?需要从头到尾依次枚举是否选择当前的数字,每选定一个数字就要去看看是不是满足“上升”的性质,这里第i个阶段就是去思考是否要选择第i个数,第i个阶段有两个状态,分别是选和不选。哈哈,依稀出现了刚刚迷宫找路的影子!咦慢着,每次当我决定要选择当前数字的时候,只需要和之前选定的一个数字比较就行了!这是和之前迷宫问题的本质不同!这就可以纵容我们不需要记录之前所有的状态啊!既然我们的选择已经不受之前状态的组合的影响了,那时间复杂度自然也不是指数的了啊!虽然我们不在乎某序列之前都是什么元素,但我们还是需要这个序列的长度的。所以我们只需要记录以某个元素结尾的LIS长度就好!因此第i个阶段的最优解只是由前i-1个阶段的最优解得到的,然后就得到了DP方程
LIS(i)=max\{LIS(j)+1\} \ \ \ \ j<i \ and\ a[j] < a[i]

所以一个问题是该用递推、贪心、搜索还是动态规划,完全是由这个问题本身阶段间状态的转移方式决定的!

每个阶段只有一个状态->递推;
每个阶段的最优状态都是由上一个阶段的最优状态得到的->贪心;
每个阶段的最优状态是由之前所有阶段的状态的组合得到的->搜索;
每个阶段的最优状态可以从之前某个阶段的某个或某些状态直接得到而不管之前这个状态是如何得到的->动态规划。

每个阶段的最优状态可以从之前某个阶段的某个或某些状态直接得到
这个性质叫做最优子结构;

而不管之前这个状态是如何得到的
这个性质叫做无后效性。

另:其实动态规划中的最优状态的说法容易产生误导,以为只需要计算最优状态就好,LIS问题确实如此,转移时只用到了每个阶段“选”的状态。但实际上有的问题往往需要对每个阶段的所有状态都算出一个最优值,然后根据这些最优值再来找最优状态。比如背包问题就需要对前i个包(阶段)容量为j时(状态)计算出最大价值。然后在最后一个阶段中的所有状态种找到最优值。

作者:王勐
链接:https://www.zhihu.com/question/23995189/answer/35429905
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。




0 0
原创粉丝点击