【解题报告】NOI1995 石子合并,POJ1179 Polygon,CH5302 金字塔——区间DP

复习几道区间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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值