复习几道区间DP的题:
NOI1995 石子合并:
题意:n个数,可以合并相邻的两个数,付出的代价是两数之和,问最后合并到一堆里的总代价最小是多少。
思路:设计状态dp[i,j]表示[i,j]区间合并付出的最小总代价,最后求dp[1,n]。
状态转移方程就是:dp[i] [j] = min{dp[i] [k] + dp[k+1] [j] + sum[i,j]} ,意思就是对于区间[i,j]的最小总代价为:枚举k,使得[i,k] 和 [k+1,j]区间的最小总代价和加上[i,j]区间数的和的值最小,就是[i,j]区间的最小总代价。
AC代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn=305;
const int INF=2e7+10;
int sum[maxn];
int f[maxn][maxn];
int n;
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n;
for(int i=1;i<=n;i++){
int num;cin>>num;
sum[i]=sum[i-1]+num;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
f[i][j]=INF;
}
}
for(int i=n;i>=1;i--){
for(int j=i;j<=n;j++){
if(i==j) f[i][j]=0;
for(int k=i;k<j;k++){
f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+sum[j]-sum[i-1]);
}
}
}
cout<<f[1][n]<<endl;
}
POJ1179 Polygon:
题意:类似石子合并,只不过多了个乘法操作。
思路:分类讨论,得到最大值的情况有:最大值+最大值,最大值x最大值,最小值 x最小值;于是我们也需要计算最小值,得到最小值的情况有:最小值+最小值,最小值x最大值,最小值x最小值。
所以直接两个dp数组分别算最大值最小值,然后还需要处理环:直接拆成2倍长度的链就行了。
AC代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn=200;
const int INF=0x3f3f3f3f;
int n;
char opt[maxn];
int a[maxn];
int dp_max[maxn][maxn],dp_min[maxn][maxn];
int RES[maxn],cnt;
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n;
for(int i=1;i<=n;i++) cin>>opt[i-1]>>a[i];
for(int i=1;i<=n;i++){
opt[i+n-1]=opt[i-1];
a[i+n]=a[i];
}
n<<=1;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
dp_min[i][j]=INF;
dp_max[i][j]=-INF;
}
}
for(int j=1;j<=n;j++){
for(int i=j;i>=1;i--){
if(i==j) dp_max[i][j]=dp_min[i][j]=a[i];
for(int k=i;k<j;k++){
if(opt[k]=='t'){
dp_max[i][j]=max(dp_max[i][j],dp_max[i][k]+dp_max[k+1][j]);
dp_min[i][j]=min(dp_min[i][j],dp_min[i][k]+dp_min[k+1][j]);
}
else{
dp_max[i][j]=max(dp_max[i][j],dp_max[i][k]*dp_max[k+1][j]);
dp_max[i][j]=max(dp_max[i][j],dp_min[i][k]*dp_min[k+1][j]);
dp_min[i][j]=min(dp_min[i][j],dp_min[i][k]*dp_min[k+1][j]);
dp_min[i][j]=min(dp_min[i][j],dp_min[i][k]*dp_max[k+1][j]);
dp_min[i][j]=min(dp_min[i][j],dp_max[i][k]*dp_min[k+1][j]);
}
}
}
}
int resN=n>>1;
int ans=-INF;
for(int i=resN;i>=1;i--){
if(dp_max[i][i+resN-1]==ans){
RES[++cnt]=i;
}
else if(dp_max[i][i+resN-1]>ans){
ans=dp_max[i][i+resN-1];
cnt=1, RES[cnt]=i;
}
}
cout<<ans<<endl;
sort(RES+1,RES+cnt+1);
for(int i=1;i<=cnt;i++) cout<<RES[i]<<" ";
cout<<endl;
}
CH5302 金字塔:
题意:给个有根树的DFS序,问你一共有多少种结构可能性。
思路:设计状态dp[i] [j]表示:对于DFS序中的[i,j]区间提供的结构可能性,最后答案为:dp[1] [n]。
状态转移方程为:
dp[i] [j] = Σ dp[i+1] [k-1]*dp[k] [j] (s[i] == s[j])
dp[i] [j] = 0 (s[i] != s[j])
意思是:首先i,j必须得保证s[i]==s[j]才符合DFS序,所以如果不等则提供的结构可能数为0。然后讨论s[i]==s[j]的情况,则对于[i,j]这颗树,可以分成两个子树[i+1,k-1],[k,j]。
比如:ABABABA:
i=1,j=7,k=3
A:进入;
B([i+1,k-1] = [2,2]):第一棵子树
ABABA([k,r] = [3,7]):第二棵子树
然后两个子树之间提供的结构可能数符合乘法原则,枚举的不同的k之间符合加法原则,所以就有上面的状态转移方程。
AC代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn=305;
const int MOD=1e9;
char s[maxn];
LL dp[maxn][maxn];
int n;
int main()
{
scanf("%s",s+1); n=strlen(s+1);
for(int i=1;i<=n;i++) dp[i][i]=1;
for(int j=1;j<=n;j++){
for(int i=j-1;i>=1;i--){
if(s[i]!=s[j]){
dp[i][j]=0;
continue;
}
for(int k=i+2;k<=j;k++)
dp[i][j]=(dp[i][j]+(dp[i+1][k-1]*dp[k][j])%MOD)%MOD;
}
}
cout<<dp[1][n]<<endl;
}