区间DP
1. 区间DP概述
-
概念:区间dp就是在区间上进行动态规划,求解一段区间上的最优解。主要是通过合并小区间的 最优解进而得出整个大区间上最优解的dp算法。
-
核心思路:既然让我求解在一个区间上的最优解,那么我把这个区间分割成一个个小区间,求解每个小区间的最优解,再合并小区间得到大区间即可。
-
两类模板:
-
常规区间DP模板:
for(int len=1;len<n;len++){ for(int l=1;l+len-1<=n;l++){ int r=l+len-1; for(int k=l;k<r;k++){ dp[l][r]=min(d[[l][r],dp[l][k]+d[k+1][r]]+temp); } } }
-
记忆化搜索模板:
int dfs(int x,int y){ //出口 for(...){ dp[..][..]=min(dp[..][..],dfs(..,..)); } }
-
2.几种的区间DP:
-
朴素版本:给你一条串,求某一段长度固定的区间和最大值。
-
例题:石子和并
-
//关键代码 for(int len=2;len<=n;len++){ for(int l=1;l+len-1<=n;l++){ int r=l+len-1; dp[l][r]=1e9; for(int k=l;k<r;k++){ dp[l][r]=min(dp[l][r],dp[l][k]+dp[k+1][r]+b[r]-b[l-1]); //b数组为前缀和 } } }
-
-
环形:在下标n与0之间断开环,同时在下标n的后面再添加一段下标0到n的元素。
-
例题:环形石子合并
-
//关键代码 for(int len=1;len<=n;len++){ for(int l=1;l+len-1<=2*n;l++){ int r=l+len-1; if(l==r) dp1[l][r]=0; for(int k=l;k<r;k++){ dp1[l][r]=min(dp1[l][r],dp1[l][k]+dp1[k+1][r]+b[r]-b[l-1]); } } }
-
-
二维区间DP:
-
例题:棋盘分隔
-
题目大意:将原棋盘割下一块矩形棋盘并使剩下部分也是矩形,再将剩下的部分继续如此分割。使最后剩下的矩形棋盘共有n块矩形棋盘。并让各矩形棋盘总分的均方差最小。
-
分析:
- 首先确定dp维数:我们可以通过两个坐标(两对(x,y))来确定矩阵大小,再用一个变量来表示可以划分几次。
- 即:
dp[x1][y1][x2][y2][x]
:矩阵左上角顶点为(x1,y1),右下角顶点为(x2,y2)的矩阵,且分成x块后,总均方差最小的值。
- 即:
- 由于维数过多,如采用常规区间DP模板,书写较为复杂。所以这里使用记忆化搜索更为方便。
- 因为需要求面积和,所以可以首先使用二位前缀和预处理。
- 再使用记忆化搜索进行DFS
- 首先判断出口条件。
- 之后由于是二维区间DP,所以它存在两种切法:横切,竖切,所以我们都需要进行考虑。
- DP转移方程为:(get为求二维前缀和)
- 横切:
dp[x1][y1][x2][y2][k]=get(x1,y1,i,y2)+dfs(i+1,y1,x2,y2,k-1)
dp[x1][y1][x2][y2][k]=get(i+1,y1,x2,y2)+dfs(x1,y1,i,y2,k-1)
- 竖切:
dp[x1][y1][x2][y2][k]=get(x1,y1,x2,i)+dfs(x1,i+1,x2,y2,k-1)
dp[x1][y1][x2][y2][k]=get(x1,i+1,x2,y2)+dfs(x1,y1,x2,i,k-1)
- 横切:
- 首先确定dp维数:我们可以通过两个坐标(两对(x,y))来确定矩阵大小,再用一个变量来表示可以划分几次。
-
//关键代码 double dfs(int x1,int y1,int x2,int y2,int k){ double &v=dp[x1][y1][x2][y2][k]; if (v>=0) return v; if(k==1) return get(x1,y1,x2,y2); v=INF; for(int i=x1;i<x2;i++){ v=min(v,get(x1,y1,i,y2)+dfs(i+1,y1,x2,y2,k-1)); v=min(v,get(i+1,y1,x2,y2)+dfs(x1,y1,i,y2,k-1)); } for(int i=y1;i<y2;i++){ v=min(v,get(x1,y1,x2,i)+dfs(x1,i+1,x2,y2,k-1)); v=min(v,get(x1,i+1,x2,y2)+dfs(x1,y1,x2,i,k-1)); } return v; }
-