算法总结—区间DP2
[USACO07OPEN] Cheapest Palindrome G
仍然是区间 DP,但时间复杂度不可能是 \(\mathcal{O}(m^3)\)。
状态
设 \(dp_{i,j}\) 表示将区间 \([i,j]\) 变成回文串的最小代价。
答案
问题是将整个字符串改为回文串,所以答案是\(dp_{1,m}\)。
状态转移方程
需要分类讨论。
-
若 \(s_i=s_j\),将区间 \([i,j]\) 改为回文串的代价就是将区间 \([i-1,j-1]\) 改为回文串的代价,即 \(dp_{i+1,j-1}\)。
-
删除左边的 \(dp_{i,j}=dp_{i+1,j}+y_{s_i}\)。
-
删除右边的 \(dp_{i,j}=dp_{i,j-1}+y_{s_j}\)。
-
增加左边的 \(dp_{i,j}=dp_{i+1,j}+x_{s_j}\)。
-
删除右边的 \(dp_{i,j}=dp_{i,j-1}+x_{s_i}\)。
初始值
初始 \(dp_{i,i}=0\),但是转移中有 \(dp_{i+1,j-1}\),所以区间长度为 \(2\) 的也需要初始化。
时间复杂度是 \(\mathcal{O}(m^2)\)。
代码
#include<bits/stdc++.h>
using namespace std;
int a[205];
int dp[2005][2005];
int main(){
int n,m;
cin>>n>>m;
string s;
cin>>s;
s=" "+s;
for(int i=1;i<=n;i++){
char c;
cin>>c;
int x,y;
cin>>x>>y;
a[c]=min(x,y);
}
for(int i=1;i<=m;i++){
for(int j=1;j<=m;j++){
dp[i][j]=1e9;
}
dp[i][i]=0;
if(s[i]==s[i+1]){
dp[i][i+1]=0;
}else{
dp[i][i+1]=min(a[s[i]],a[s[i+1]]);
}
}
for(int len=3;len<=m;len++){
for(int i=1;i+len-1<=m;i++){
int j=i+len-1;
if(s[i]==s[j]){
dp[i][j]=dp[i+1][j-1];
}else{
dp[i][j]=min(dp[i][j-1]+a[s[j]],dp[i+1][j]+a[s[i]]);
}
}
}
cout<<dp[1][m];
return 0;
}
这种只能增加一个或者删除一个的时间复杂度一般是 \(\mathcal{O}(n^2)\)。
[USACO06FEB] Treats for the Cows G/S
状态
设 \(dp_{i,j}\) 表示将区间 \([i,j]\) 的零食卖完所获得的最大价值。
答案
\(dp_{1,n}\)。
状态转移方程
\(t=n-(j-i+1)+1\),表示一个零食是第几天卖出的。
需要分类讨论。
-
卖出增左边的 \(dp_{i,j}=dp_{i+1,j}+a_i\times t\)。
-
卖出增右边的 \(dp_{i,j}=dp_{i,j-1}+a_i\times t\)。
\(dp_{i,j}\) 就是这两种情况取最大值。
初始值
初始 \(dp_{i,i}=a_i\times n\)。
时间复杂度 \(\mathcal{O}(n^2)\)。
代码
#include<bits/stdc++.h>
using namespace std;
int a[2005];
int dp[2005][2005];
int main(){
int n;
cin>>n;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
dp[i][j]=-1e9;
}
}
for(int i=1;i<=n;i++){
cin>>a[i];
dp[i][i]=a[i]*n;
}
for(int len=2;len<=n;len++){
for(int i=1;i+len-1<=n;i++){
int j=i+len-1;
dp[i][j]=max(dp[i][j-1]+a[j]*(n-len+1),dp[i+1][j]+a[i]*(n-len+1));
}
}
cout<<dp[1][n];
return 0;
}
[HNOI2010] 合唱队
状态
\(dp_{i,j,0}\) 表示队列中最后进去的是 \(i\) 的方案数。
\(dp_{i,j,1}\) 表示队列中最后进去的是 \(j\) 的方案数。
答案
\(dp_{1,n,0}+dp_{1,n,1}\)。
状态转移方程
-
\(h_i<h_{i+1}\),\(dp_{i,j,0}=dp_{i,j,0}+dp_{i+1,j,0}\)。
-
\(h_i<h_j\),\(dp_{i,j,0}=dp_{i,j,0}+dp_{i+1,j,1}\)。
-
\(h_j>h_{j-1}\),\(dp_{i,j,1}=dp_{i,j,1}+dp_{i,j-1,1}\)。
-
\(h_j>h_i\),\(dp_{i,j,1}=dp_{i,j,1}+dp_{i,j-1,0}\)。
初始值
要么 \(dp_{i,i,0}=1\),要么 \(dp_{i,i,1}=1\),否则在转移过程中会重复。
代码
#include<bits/stdc++.h>
using namespace std;
const int mod=19650827;
int a[1005];
int dp[1005][1005][2];
int main(){
int n;
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
dp[i][i][0]=1;
}
for(int len=2;len<=n;len++){
for(int i=1;i+len-1<=n;i++){
int j=i+len-1;
if(a[i]<a[i+1]){
dp[i][j][0]=(dp[i][j][0]+dp[i+1][j][0])%mod;
}
if(a[i]<a[j]){
dp[i][j][0]=(dp[i][j][0]+dp[i+1][j][1])%mod;
}
if(a[j]>a[j-1]){
dp[i][j][1]=(dp[i][j][1]+dp[i][j-1][1])%mod;
}
if(a[j]>a[i]){
dp[i][j][1]=(dp[i][j][1]+dp[i][j-1][0])%mod;
}
}
}
cout<<(dp[1][n][0]+dp[1][n][1])%mod;
return 0;
}