区间dp入门+例题
区间dp作为线性dp的一种,顾名思义是以区间作为阶段进行dp的,使用它的左右端点描述每个维度,决策往往是从小状态向大状态转移中推得的。它跟st表等树状结构有着相似的原理---向下划分,向上递推。
dp最终要求的就是推出状态转移方程,从板子中我们可以感受出来区间dp的关键在于如何找到小状态与大状态的关系。
for(int i=1;i<n;i++){//区间长度 for(int l=1;l+i<=n;l++){//左端点 for(int h=l;h<l+i;h++)//枚举区间合并的分割点找到最优解 //转移方程 } }
这样基本的板子时间复杂度会达到o(n^3),如果被卡的话通常就要从四边形不等式等状态转移的性质出发能不能找到更好的转移,优化掉冗余。dp是拿空间换取时间的搜索,只要优化掉冗余推平不是梦。(蒟蒻口胡orz~)
刷了一点蓝书上的例题,分享一下存个档
石子合并
非常经典的区间dp题,肉眼可见dp[l][r]=minrk=l(dp[l][r],dp[l][k]+dp[k+1][r])
#include<bits/stdc++.h> using namespace std; int dp[305][305]; int sum[305]; int main() { int n;scanf("%d",&n); memset(dp,0x3f,sizeof dp); for(int i=1;i<=n;i++) scanf("%d",&dp[i][0]),sum[i]=sum[i-1]+dp[i][0],dp[i][i]=0; for(int i=1;i<n;i++){//区间长度 for(int l=1;l+i<=n;l++){//左端点 for(int h=l;h<l+i;h++)//枚举区间合并的分割点找到最优解 dp[l][l+i]=min(dp[l][h]+dp[h+1][l+i]+sum[l+i]-sum[l-1],dp[l][l+i]);//转移方程 } } cout<<dp[1][n]<<endl; }
Polygon
枚举删掉的第一条边,就跟上一题类似了,由于存在乘,同时维护最大和最小的状态转移一下就OK啦
#include<bits/stdc++.h> using namespace std; char c[55][55]; long long dp[55][55][2],num[55]; vector <int> v,ans; int main() { int n;scanf("%d",&n);getchar(); for(int i=1;i<=2*n;i++){ if(i&1){ int t1=i/2,t2=i/2+1; if(t1==0) t1=n; scanf("%c",&c[t1][t2]); c[t2][t1]=c[t1][t2]; } else scanf("%lld",&num[i/2]),getchar(); } int tot=1,mi=-1e9; for(int i=1;i<=n;i++){ v.clear();v.push_back(0); for(int j=i;j<=n;j++) v.push_back(j); for(int j=1;j<i;j++) v.push_back(j); for(int j=1;j<=n;j++) for(int h=1;h<=n;h++) dp[j][h][0]=-1e18,dp[j][h][1]=1e18; for(int j=1;j<=n;j++) dp[j][j][0]=dp[j][j][1]=num[v[j]]; for(int j=1;j<n;j++){ for(int l=1;l+j<=n;l++){ for(int k=l;k<l+j;k++){ long long t1=1e18,t2=-1e18; if(c[v[k]][v[k+1]]=='t') t1=min(t1,dp[l][k][1]+dp[k+1][l+j][1]), t2=max(t2,dp[l][k][0]+dp[k+1][l+j][0]); else{ for(int p=0;p<2;p++) for(int q=0;q<2;q++) t1=min(t1,dp[l][k][p]*dp[k+1][l+j][q]), t2=max(t2,dp[l][k][p]*dp[k+1][l+j][q]); } dp[l][l+j][0]=max(dp[l][l+j][0],t2); dp[l][l+j][1]=min(dp[l][l+j][1],t1); } } } if(dp[1][n][0]>mi){ mi=dp[1][n][0];ans.clear();ans.push_back(tot); } else if(dp[1][n][0]==mi) ans.push_back(tot); tot++; } cout<<mi<<endl; int l=ans.size(); for(int i=0;i<l;i++) printf("%d%c",ans[i],i==l-1?'\n':' '); }
当然这个还有优化成o(n^3)的写法,对枚举删第一条进行优化。
金字塔
阅读题。。。dp[l][r]表示s[l]-s[r]的可以构成的个数,那么枚举k为第一个子树的分割点就可以得出
dp[l][r]=Σdp[l][k-1]+dp[k][r-1]
#include<bits/stdc++.h> using namespace std; const int maxn=3e2+10; const int mod=1e9; char s[maxn]; int dp[maxn][maxn]; int main() { scanf("%s",s+1);int n=strlen(s+1); for(int i=1;i<=n;i++) dp[i][i]=1; for(int l=n-1;l>=1;l--) for(int r=l+1;r<=n;r++){ if(s[l]==s[r]){ for(int k=l+1;k<r;k++) dp[l][r]=(dp[l][r]+1LL*dp[l][k-1]*dp[k][r-1]%mod)%mod; } } cout<<dp[1][n]<<endl; }