区间 d p dp dp 是动态规划的一种,一般用于解决贪心不能解决的区间问题。
例:石子合并(弱化版)
思路:
构建模型: n n n 堆石子
状态:
f l , r f_{l,r} fl,r 表示从 l 到 r 合并成最小代价。
-
先把区间 [ l , r ] [\,l,r\,] [l,r] 用 k k k 切成两部分: [ l , k ] [\,l,k\,] [l,k] 和 [ k + 1 , r ] [\,k+1,r\,] [k+1,r], k k k 是切分点。
-
在把两部分 [ l , k ] [\,l,k\,] [l,k] 和 [ k + 1 , r ] [\,k+1,r\,] [k+1,r] 合并起来。
转移方程: f l , r = f l , k + f k + 1 , r + ( s r − s l − 1 ) f_{l,r}=f_{l,k}+f_{k+1,r}+(s_r-s_{l-1}) fl,r=fl,k+fk+1,r+(sr−sl−1)
计算:
f l , k + f k + 1 , r + ( s r − s l − 1 ) f_{l,k}+f_{k+1,r}+(s_r-s_{l-1}) fl,k+fk+1,r+(sr−sl−1)
其中 s r − s l − 1 s_r-s_{l-1} sr−sl−1 表示 [ l , r ] [\,l,r\,] [l,r] 的石子质量,因为合并两堆石子花费两堆石子的质量和。
初值:
f i , i = 0 f_{i,i}=0 fi,i=0(每堆石子本身为 0 0 0,其余为无穷)。
目标:
f 1 , n f_{1,n} f1,n 为所有合并成一堆的最小代价。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=3e2+10;
int n,m[N],ans,f[N][N],s[N];
signed main(){
cin>>n;
memset(f,0x3f,sizeof(f));
for(int i=1;i<=n;i++){
cin>>m[i];
s[i]=s[i-1]+m[i]; //前缀和
f[i][i]=0;
}
for(int len=2;len<=n;len++){ //阶段:枚举区间长度
for(int l=1;l+len-1<=n;l++){ //状态:枚举区间起点
int r=l+len-1; //区间终点
for(int k=l;k<r;k++){ //决策:枚举分割点
f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]+s[r]-s[l-1]);
}
}
}
cout<<f[1][n];
return 0;
}
看完上面,大家对一条链的区间 d p dp dp 都有所了解了,那如果要在环上进行区间 d p dp dp 呢?
石子合并
思路:
正如题目所讲,所有石子组成了一个环,我们需要想办法把它转化成链来解。
-
思路一:把链的某条边切断,这样就成了一条链,然后用模板做即可。但这种做法有个弊端:我们需要枚举被切断的边,也就是说在模板 O ( n 3 ) O(n^3) O(n3) 的基础上再加一层,变成了 O ( n 4 ) O(n^4) O(n4),在比赛中拿不到满分的。
-
思路二:把环切开,在复制一遍,两条链连起来(如下图)。这样子我们就可以做到在区间长度确定的情况下,只移动区间位置而达到环上区间 d p dp dp 的效果。正如下图, ( 1 − 5 ) (1-5) (1−5)、 ( 3 − 2 ) (3-2) (3−2)、 ( 5 − 4 ) (5-4) (5−4) 都是其中的合法情况。因此,我们只扩大了一点点空间( O ( n ) ⇒ O ( 2 n ) \small O(n)\Rightarrow O(2n) O(n)⇒O(2n)),换来了时间的维护( O ( n 3 ) ⇒ O ( n 3 ) \small O(n^3)\Rightarrow O(n^3) O(n3)⇒O(n3)),非常香。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=310;
int a[N],s[N],f[N][N],F[N][N],_maxn=-1e9,_minn=1e9;
int n;
int main(){
memset(f,0x3f,sizeof(f));
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
a[i+n]=a[i];
}
for(int i=1;i<=2*n;i++){
s[i]=s[i-1]+a[i];
f[i][i]=0;
F[i][i]=0;
}
for(int len=2;len<=n;len++){
for(int l=1;l+len-1<=2*n;l++){
int r=l+len-1;
for(int k=l;k<r;k++){
F[l][r]=max(F[l][r],F[l][k]+F[k+1][r]+s[r]-s[l-1]);
f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]+s[r]-s[l-1]);
}
}
}
for(int i=1;i<=n;i++){
_maxn=max(_maxn,F[i][i+n-1]);
_minn=min(_minn,f[i][i+n-1]);
}
cout<<_minn<<"\n"<<_maxn;
return 0;
}
综上所述,区间 d p dp dp 也就是要根据题目确定区间大小,确定遍历范围,确定转移目标,最后改个模板就可以快乐 AC \operatorname{AC} AC 了。