区间DP
区间型DP是DP的一种模型,它主要以区间的长度作为区间的阶段,使用两个坐标来记录状态,向左或向右进行扩张来完成转移,也可以说是从它的子区间中转移得来。
个人觉得这种模型可能还比较好看出来一点,而且状态设计也相差不多,都一定会维护左端点和右端点。
下面扔几道例题上来
最经典的例题自然是石子合并问题了
有
若
也可以说,一个长度为
所以我们应该将区间的长度放在第一维,保证它的传递性
然后枚举这堆石子的左端点,得到一个区间
伪代码如下:
for(int i=2;i<=n;i++){//区间长度
for(int l=1;l<=n;l++){//枚举左端点
int r=l+i-1;
if(r>n) break;//得到右端点,判断合法性
for(int k=l;k<r;k++){//枚举中间点
f[i][j]="转移内容"
}
}
}
例题 P1880石子合并
这道题和上面有一点不同,首先它需要求出最大值和最小值,其次这道题的背景是一个环,也就是说第
断环为链
我们将原数组复制一遍(最后一个数可以不计),然后得到了一个长度为
事实上除了DP,还有一些题目也会用到断环为链的技巧。这种方法可以让你省很大事。
接下来一道例题P1063 能量项链,跟上面那道基本一个思路,都是比较经典的区间 DP 问题
第二种题型,也是需要记录区间,但是每个点进入区间的时间会影响到它的价值,其实本质上跟前一种题型一样。
例题P2858 奶牛零食
我们每次取出都只能在队列两段取,那么我们换一种思路倒过来想,先选择一个点,每次从它的左右两端放回,是不是跟这个问题一样?
那么这道题的方程就显现出来了
我们同样是枚举区间长度,区间左端点,得到了一个区间,然后我们判断这个区间是从什么地方转移过来的。
如前面的分析,想得到
那么我们的转移方程就出来了
初始化的时候把区间长度为
有一道跟这个差不多的P1005 矩形取数游戏
对于这道题,实际上每一行之间是没有干扰的,我们对于每一行进行一次跟上文差不多的
代码如下
void work(){
for(int i=1;i<=m;i++){
f[i][i]=a[i]*ksm(2,m);//我用的快速幂,可以使用预处理
}
for(int i=1;i<=m;i++){//长度
for(int l=1;i<=m;l++){//左端点
int r=l+i;
if(r>m) break;
f[l][r]=max(f[l+1][r]+a[l]*ksm(2,m-r+l),f[l][r-1]+a[r]*ksm(2,m-r+l));
}
}
ans+=f[1][m];//每行的答案可以统计
}
int main() {
n=read(),m=read();
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
a[j]=read();
}
work();
}
print(ans);
return 0;
}
由于本题答案爆 long long 了,我是用 int128 水过去的,正解是高精度乘法,有兴趣的小伙伴可以码一下。
第三种题型,你在区间的左端点和你在区间的右端点情况不同
例题 P1220 关路灯
我们设计的状态为
然后我们在转移过程中同时转移最左端和最右端的情况
很明显
而
计算好中间转移的差值,做好预处理,我们就可以得出转移的过程
for(int k=2;k<=n;k++){
for(int i=1;i+k-1<=n;i++){
int j=i+k-1;
int s1=sum[n]+sum[i]-sum[j],s2=(sum[n]+sum[i-1]-sum[j-1]);
f[i][j][0]=min(f[i+1][j][0]+(a[i+1]-a[i])*s1,f[i+1][j][1]+(a[j]-a[i])*s1);
f[i][j][1]=min(f[i][j-1][0]+(a[j]-a[i])*s2,f[i][j-1][1]+(a[j]-a[j-1])*s2);
}
}
最后输出的时候输出
补充例题 P3146 248
这题和第一种差不多,我们以
需要注意的是最大值的答案不一定会是
int n=read();
for(int i=1;i<=n;i++){
f[i][i]=read();
}
int ans=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
int r=i+j;
if(r>n) break;
for(int k=j;k<=r;k++){
if(f[j][k]==f[k+1][r]){
f[j][r]=max(f[j][r],f[j][k]+1);
}
}
ans=max(ans,f[j][r]);
}
}
cout<<ans;
接下来是这道题的加强版 P3147 262144
由于数据范围加强了,
利用一种类似于倍增的思想
设
得到转移方程
int n=read();
for(int i=1;i<=n;i++){
int d=read();
f[d][i]=i+1;
}
for(int i=1;i<=60;i++){
for(int j=1;j<=n;j++){
if(f[i][j]==0) f[i][j]=f[i-1][f[i-1][j]];
if(f[i][j]!=0) ans=max(ans,i);
}
}
cout<<ans;
return 0;
其实这一部分我也没有太理解,先鸽了