区间DP
前面已经提过,线性DP问题是有着较为明显的阶段划分的动态规划问题,一般是由初态一步一步转移到终态。但,同属于线性DP的区间DP与前面提到的却有着较大不同,形象的说,区间DP的转移像是“逐渐扩张的领土”。
具体来讲,区间DP的阶段是“区间长度”,并且用左右端点的下标来表示其状态。此外,区间长度较长的阶段是由区间长度较短的阶段转移来的,这一点似乎与“线段树”有些相似。
下面,还是看一道模板题。
设有 \(N(N \le 300)\) 堆石子排成一排,其编号为 \(1,2,3,\cdots,N\)。每堆石子有一定的质量 \(m_i(m_i \le 1000)\)。现在要将这 \(N\) 堆石子合并成为一堆。每次只能合并相邻的两堆,合并的代价为这两堆石子的质量之和,合并后与这两堆石子相邻的石子将和新堆相邻。合并时由于选择的顺序不同,合并的总代价也不相同。试找出一种合理的方法,使总的代价最小,并输出最小代价。
解决动态规划问题的关键在于推出决策(即“转移方程”),那这道题该怎么推出决策呢?
首先,假设我们已经得到\(l\)到\(r\)中区间长度\(len\)小于\(r-l+1\)的阶段的最优解,怎么推出此阶段的最优解\(dp[i][j]\)呢?
那当然是…………
枚举!!!
对,只需要无脑枚举就行,把\(l\)到\(r\)分为两段,枚举分界点\(k\),然后取最小值就行。
话不多说,上代码!
#include<bits/stdc++.h>
using namespace std;
const int N=305;
int n,a[N],dp[N][N],b[N];
int dfs(int l,int r){
if(l==r) return dp[l][r]=0;
if(dp[l][r]!=0) return dp[l][r];
for(int i=l;i<r;i++){
if(!dp[l][r]) dp[l][r]=dfs(l,i)+dfs(i+1,r)+b[r]-b[l-1];
else dp[l][r]=min(dp[l][r],dfs(l,i)+dfs(i+1,r)+b[r]-b[l-1]);
}
return dp[l][r];
}
int main(){
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i],b[i]=a[i]+b[i-1];
cout<<dfs(1,n);
return 0;
}
你应该注意到,这里我们没有像以前那样那样用递推的方法来求,而是用递归来求,这实际上就是解决动态规划问题中常用的“记忆化搜索”,其实记忆化搜索与一般的递归没太大区别,只是比一般递归多了记录阶段状态的数组,这可以在重复求某段区间时节省很多时间,这也是动态规划的实质——以空间换时间。
当然用递推也是可求的,只要用三重循环分别表示阶段,状态,决策,即先枚举区间长度,再枚举左右端点,最后枚举分界点。
这种写法就交给各位自己实现吧。绝对不是笔者懒
最后,前缀和什么的就不再赘述了,我们只需要注意此类问题的特点,借助模板中思考代码的流程,就可理解“套路”。值得一提的是,此类问题还常有环形,我们只需用些小技巧(比如复制一遍接在后面),就可转化为经典的区间DP问题来解决。

浙公网安备 33010602011771号