开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第7天,点击查看活动详情
区间DP
区间DP,是指状态转移发生在区间上的动态规划问题,分析区间DP问题常常考虑的点为:区间分界点、端点、题中的特殊点
普通区间DP
例题:
分析:
状态表示: 用f[i] [j]表示将[i~j]闭区间合并为1堆的方案全体
状态转移: 这里就是用区间分界点作为考虑点,我们假设最后一步是将[i~k]和[k+1,j]合并为1堆,那么我们很容易写出下面的方程
AC代码:
#include<iostream>
using namespace std;
const int N=1010;
int n;
int s[N];
int f[N][N];
int main(){
cin>>n;
for(int i=1;i<=n;i++){
int w;
cin>>w;
s[i]=s[i-1]+w;
}
for(int len=2;len<=n;len++){
for(int i=1;i+len-1<=n;i++){
int j=i+len-1;
f[i][j]=1e9;
for(int k=i;k<j;k++){
f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+s[j]-s[i-1]);
}
}
}
cout<<f[1][n]<<endl;
return 0;
}
环形区间DP
例题:
处理手段: 遇到环,可以扩展两倍长度变为链
状态表示: 用f[i] [j] 表示,从边 i 到边 j运算后的所有答案总数
状态转移: 我们仍然可以考虑分界点,但是这题有负数,我们不妨求一个最小一个最大,综合考虑以得到最大的解,于是我们还可以拓展一维,f[i,j,0]表示最大值,f[i,j,1]表示最小值,状态转移方程如下:
方程中a,b是两个待枚举值,cal函数是计算相应运算符号对应的值,最小值的情况与之类似:
AC代码:
#include<iostream>
using namespace std;
const int N=120;
const int INF=1e9;
int n;
char op[N];
int q[N];
int f[N][N][2];
int cal(int a,int b,char op){
if(op=='t'){
return a+b;
}
return a*b;
}
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>op[i]>>q[i];
op[i+n]=op[i];q[i+n]=q[i];
}
for(int i=1;i<=2*n;i++){
for(int j=1;j<=2*n;j++){
if(i==j)f[i][j][0]=f[i][j][1]=q[i];
else{f[i][j][0]=-INF;f[i][j][1]=INF;}
}
}
for(int len=2;len<=n;len++){
for(int i=1;i+len-1<=2*n;i++){
int j=i+len-1;
for(int k=i;k<j;k++){
for(int a=0;a<=1;a++){
for(int b=0;b<=1;b++){
f[i][j][0]=max(f[i][j][0],cal(f[i][k][a],f[k+1][j][b],op[k+1]));
f[i][j][1]=min(f[i][j][1],cal(f[i][k][a],f[k+1][j][b],op[k+1]));
}
}
}
}
}
int res[20002];
int idx=0,maxn=-INF;
for(int i=1;i<=n;i++){
int t=f[i][i+n-1][0];
if(t>maxn){
idx=0;maxn=t;res[idx++]=i;
}
else if(t==maxn){
res[idx++]=i;
}
}
cout<<maxn<<endl;
for(int i=0;i<idx;i++){
cout<<res[i]<<" ";
}
return 0;
}