最近学习了一下一些简单的区间dp的题目。
蓝书和西北工业大学ACM2017暑假集训 - 区间DP这个。
1.石子合并
最多300个石子堆按顺序排列在一条数轴上,每次选择相邻两堆进行合并,合并代价为两堆石子石子数量之和,要求将所有石子堆合并为一堆最小化合并代价。
考虑
d
p
[
l
]
[
r
]
dp[l][r]
dp[l][r]为合并第
l
l
l到
r
r
r堆石子的最小代价。
则一个显然的状态转移为枚举中间点
k
k
k,作为最后一次合并,则有
d
p
[
l
]
[
r
]
=
m
i
n
(
d
p
[
l
]
[
k
]
+
d
p
[
k
+
1
]
[
r
]
+
s
u
m
[
r
]
−
s
u
m
[
l
−
1
]
)
dp[l][r]=min(dp[l][k]+dp[k+1][r]+sum[r]-sum[l-1])
dp[l][r]=min(dp[l][k]+dp[k+1][r]+sum[r]−sum[l−1])合并
[
l
,
k
]
[l,k]
[l,k]堆的代价+合并
[
k
+
1
,
r
]
[k+1,r]
[k+1,r]堆的代价+最后一次合并代价。
那么如果知道了较小数量的堆之间的合并,就可以向更大的堆数合并进行递推。
于是可以将合并堆数作为dp阶段,将合并区间作为状态,将枚举中间点作为决策,进行状态转移。
初始态为:
i
∈
[
1
,
n
]
,
d
p
[
i
]
[
i
]
=
该
堆
石
子
数
i\in[1,n] ,dp[i][i]=该堆石子数
i∈[1,n],dp[i][i]=该堆石子数,其余则为inf。
时间复杂度:
o
(
n
3
)
o(n^3)
o(n3)。
#include<bits/stdc++.h>
using namespace std;
const int maxn=309;
int a[maxn];
int dp[maxn][maxn];
int main(){
int n;
scanf("%d",&n);
for(int i=1;i<=n;++i){
scanf("%d",&a[i]);
a[i]+=a[i-1];
}
memset(dp,0x3f,sizeof(dp));
for(int i=1;i<=n;++i) dp[i][i]=0;
for(int len=2;len<=n;++len)
for(int l=1;l<=n-len+1;++l){
int r=l+len-1;
for(int k=l;k<r;++k)
dp[l][r]=min(dp[l][r],dp[l][k]+dp[k+1][r]+a[r]-a[l-1]);
}
printf("%d\n",dp[1][n]);
return 0;
}
2.多边形
题意看题,对于这种环状的,只要将原序列复制一份拼接在末尾即可。
切断某处之后即转化为石子合并问题,但该题多了一个乘法,并且题目数据范围会出现负数,那么两个负数相乘可能就会取代已有的最大值,所以需要同时维护区间
[
l
,
r
]
[l,r]
[l,r]进行操作后得到的最大值与最小值。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=109;
int a[maxn];
int num[maxn]; //0 + 1 *;
int dp[maxn][maxn][2];
char s[9];
const int inf=0x3f3f3f3f;
vector<int> v;
int main(){
int n;
scanf("%d",&n);
for(int i=1,x;i<=n;++i){
scanf("%s%d",s,&x);
if(s[0]=='x') num[i]=1;
//cout<<i<<" "<<(i+1)%n<<endl;
if((i+1)%n==0) a[n]=x;
else a[(i+1)%n]=x;
}
for(int i=1;i<=2*n;++i)
for(int j=1;j<=2*n;++j) dp[i][j][1]=-inf,dp[i][j][0]=inf;
for(int i=1;i<=n;++i) num[i+n]=num[i],a[i+n]=a[i];
//for(int i=1;i<=2*n;++i) cout<<a[i]<<" ";cout<<endl;
//for(int i=1;i<=2*n;++i) cout<<num[i]<<" ";cout<<endl;
for(int i=1;i<=2*n;++i) dp[i][i][0]=dp[i][i][1]=a[i];
for(int len=2;len<=2*n;++len)
for(int l=1;l<=2*n-len+1;++l){
int r=l+len-1;
for(int k=l;k<r;++k)
if(num[k]){
dp[l][r][1]=max(dp[l][r][1],dp[l][k][1]*dp[k+1][r][1]);
dp[l][r][1]=max(dp[l][r][1],dp[l][k][0]*dp[k+1][r][0]);
dp[l][r][0]=min(dp[l][r][0],dp[l][k][0]*dp[k+1][r][1]);
dp[l][r][0]=min(dp[l][r][0],dp[l][k][1]*dp[k+1][r][0]);
dp[l][r][0]=min(dp[l][r][0],dp[l][k][0]*dp[k+1][r][0]);
}
else{
dp[l][r][1]=max(dp[l][r][1],dp[l][k][1]+dp[k+1][r][1]);
dp[l][r][0]=min(dp[l][r][0],dp[l][k][0]+dp[k+1][r][0]);
}
}
int res=-0x3f3f3f3f;
for(int i=1;i<=n;++i) res=max(res,dp[i][i+n-1][1]);
printf("%d\n",res);
for(int i=1,pos=n;i<=n;++i,pos=(pos+1)%n)
if(dp[i][i+n-1][1]==res) v.push_back(pos);
sort(v.begin(),v.end());
for(auto x:v) printf("%d ",x);
return 0;
}
3.括号匹配
up主视频中
定义
d
p
[
l
]
[
r
]
dp[l][r]
dp[l][r]为l~r的字符串中选取一些字符形成的子序列可以得到的最大数量的匹配括号。
那么可以有如下转移方程:
- 对于添加一层括号,若s[l]与s[r]为一对括号,则 d p [ l ] [ r ] dp[l][r] dp[l][r]可以由 d p [ l + 1 ] [ r − 1 ] + 2 dp[l+1][r-1]+2 dp[l+1][r−1]+2转移来。
- 平常情况则是由
k
∈
[
l
,
r
)
,
m
a
x
(
d
p
[
l
]
[
k
]
+
d
p
[
k
+
1
]
[
r
]
)
k\in[l,r),max(dp[l][k]+dp[k+1][r])
k∈[l,r),max(dp[l][k]+dp[k+1][r])
那么阶段,状态,决策均与石子合并一致。
决策单调性与四边形不等式优化
咕咕咕,太难了,还没学会。。。。