学习笔记——区间DP
前言
区间DP也属于线性DP的一种,它以 “区间长度” 作为DP的 “阶段” ,使用 区间的左右端点 描述每一个维度。在区间DP中,一个状态由若干个比它更小且包含于它的区间转移而来(这也决定了 区间DP的决策就是不同的划分区间的方法 )。初始时一般由区间长度为1的元区间构成,而我们要求的最终状态,一般都是整个区间。
怎么样?是不是觉得区间DP和线段树有点像,线段树是将子节点的信息向上传递到父节点,区间DP也是将子区间的信息传递到我们要求的区间。它们虽然一个属于DP,一个属于数据结构,但思想上都大同小异。所以我们学习OI要注重思想上的融会贯通
,才能在学习上事半功倍。
好了好了,让我们正式踏入区间DP的殿堂吧!
1.区间DP的定义
顾名思义,区间DP就是在给定的一段区间上进行DP,求解该区间上的最优解。
2.区间DP的一般思路
先将要求的区间分割成一个个小区间,求出每一个小区间的最优解,然后再枚举合并点,通过合并点合并相邻的区间,找出并记录要求的区间的最优解。
设\(f[i][j]\)表示下标位置从\(i\)到\(j\)这一段区间的最优解,这状态转移方程为
其中的cost就是合并区间的代价。
3.怎样进行区间DP的状态转移
注意前言部分,我们说过,区间DP的区间长度就是状态,所以我们可以枚举区间的长度,再不断枚举左端点,通过公式\(j=i+len-1\)得到右端点,最后不断枚举合并点进行合并操作即可。
用代码说话(下面的代码进行了断环为链的优化),就是:
for(R int len=2;len<=n;++len){//枚举区间长度
for(R int i=1;i<=(n<<1)-len+1;++i){//枚举左端点
int j=i+len-1;//公式计算右端点
for(R int k=i;k<j;++k){//枚举合并点
f[i][j]=max(f[i][j],f[i][k]+f[k+1][j]+sum[j]-sum[i-1]);//进行状态转移
}
}
}
4.断环为链
既然给的模板代码写了断环为链的操作,那我们肯定要讲解一波。
1.首先,来一个最好理解的方法--枚举每一个断点,然后将其切断变为一条链,对每一条链都进行一次区间DP。显然,这么做的时间复杂度为\(O(n^4)\)(果然是人傻常数大),拿这个去做题不被\(T\)飞才怪。(这种没有用的方法就不上代码了吧)
2.既然不动脑子的做法会被\(T\)飞,那我们动动脑子优化不就完了。我们可以将这个环先断成一条链,再将这条链原原本本的复制一遍,按顺序接到这条链的末尾。最后统计答案的时候从\(1\)到\(n-1\)找出长度为\(n\)的区间的最优解即可。这么做,时间复杂度就将为了可靠的\(O(n^3)\)。用代码说话,就是:(就是上面的代码)
for(R int len=2;len<=n;++len){//枚举区间长度
for(R int i=1;i<=(n<<1)-len+1;++i){//枚举左端点
int j=i+len-1;//公式计算右端点
for(R int k=i;k<j;++k){//枚举合并点
f[i][j]=max(f[i][j],f[i][k]+f[k+1][j]+sum[j]-sum[i-1]);//进行状态转移
}
}
}
好了好了,区间DP就这些内容,做几道例题就可以了。
2.分数组分别记录区间信息的经典题(题解)(我的代码 (其实是照着书上写的) )
3.变一下状态转移方程其他都是板子的区间DP(题解)(我的代码)
通过本题,我们可以得出经验--区间DP的目标不一定是整个区间,也可能是一些符合题意的子区间,我们要理清题意,明白DP的目标,然后根据目标进行解题。
本题虽然是T3的加强版,但它超大的数据范围让我们无法开二维区间DP的数组进行状态转移,所以我们要理解区间DP的本质,可以将二维DP数组中一维和DP数组的值进行互换,令\(f[i][j]\)记录以\(i\)为左端点合并成数值为\(j\)时的区间右端点的下标,然后再推导出DP方程进行求解。
本题也是一道经典的板子,要考虑能否将当前点需要涂的颜色与之前涂过的点共用一次操作次数。我们可以默认该区间的第一个点最先涂一次颜色,如果新的点与区间第一个点同色,就可以共用一次操作次数,否则枚举最佳合并点合并左右区间。
(如果该点(设为\(i\))与区间中一点(设为\(j\))可以共用一次操作次数,那么在以\(j\)为开头的区间一定会共用一次操作次数,枚举最佳合并点合并左右区间时就可以合并到)
本题考察思维的完备性,要考虑到单一状态可能不足以达到目标(负负得正,而负数是最小值),所以我们要开两个DP数组分别记录最大值和最小值,然后再进行状态转移。
先挖个坑,以后再做


浙公网安备 33010602011771号