区间DP训练集锦

第一题 石子合并问题--直线版

 HRBUST - 1818 

可以自行在vj上查题,最基础的石子问题,昨天没过,今天过了,vj有毒。

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=101;
const int INF=1<<30;
int Min[maxn][maxn];
int Max[maxn][maxn];
int a[maxn],sum[maxn];
int main()
{
  int n;
  while(cin>>n&&n!=0)
  {
    for(int i=1;i<=n;i++)
    {
      cin>>a[i];
    }
    sum[0]=0;
    for(int i=1;i<=n;i++)
    {
      Min[i][i]=0;
      Max[i][i]=0;
      sum[i]=sum[i-1]+a[i];
    }
    for(int v=2;v<=n;v++)
    {
      for(int i=1;i<=n-v+1;i++)
      {
        int j=i+v-1;//j代表终点
        Min[i][j]=INF;
        Max[i][j]=-1;
        int tmp=sum[j]-sum[i-1];
        for(int k=i;k<j;k++)
        {
          Min[i][j]=min(Min[i][j],Min[i][k]+Min[k+1][j]+tmp);
          Max[i][j]=max(Max[i][j],Max[i][k]+Max[k+1][j]+tmp);
        }
      }
    }
    cout<<Min[1][n]<<" ";
    cout<<Max[1][n]<<endl;
  }
  return 0;
}

第二题 石子合并问题--圆形版

 HRBUST - 1819 

就是上一题的一个进阶, 但是我找bug找了很久都没有找到。我是真的不知道为啥。样例都能过。

第三题 Brackets

 POJ - 2955 

这道题其实就相当于石子合并直线版的变形问题。他动态规划方程就是dp[i][j]=max(dp[i][j],dp[i+1][k-1]+dp[k+1][j]+2)相当于第i个和第k个匹配好。思路逻辑很简单,就是要注意边界问题,字符串是从0开始的还是从1开始数的。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=101;
char str[maxn];
int dp[maxn][maxn];
//dp[i][j]表示从第i个到第j个括号的最大匹配数目
int main()
{
  while(scanf("%s",str+1)&&str[1]!='e')
  {
    memset(dp,0,sizeof(dp));
    int len=strlen(str+1);
    for(int i=len-1;i>=0;i--)
    {
      for(int j=i+1;j<=len;j++)
      {
        dp[i][j]=dp[i+1][j];//没有匹配到
        for(int k=i+1;k<=j;k++)//枚举区间i+1---j
        {
          if((str[i]=='('&&str[k]==')')||(str[i]=='['&&str[k]==']'))
          {
            dp[i][j]=max(dp[i][j],dp[i+1][k-1]+dp[k+1][j]+2);
            //第i个和第k个匹配到了
          }
        }
      }
    }
    cout<<dp[1][len]<<endl;
  }
  return 0;
}

第四题 Halloween Costumes

 LightOJ - 1422  

版本一 55ms 

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=101;
int dp[maxn][maxn];
int a[maxn];
int main()
{
  int t,cnt=0;
  cin>>t;
  while(t--)
  {
    cnt++;
    int n;
    cin>>n;
    memset(dp,0,sizeof(dp));
    for(int i=1;i<=n;i++)
    {
      cin>>a[i];
    }
    for(int i=n;i>=1;i--)//从后往前
    {
      for(int j=i;j<=n;j++)
      {
        dp[i][j]=dp[i+1][j]+1;
        for(int k=i+1;k<=j;k++)
        {
          if(a[i]==a[k])
          {
            dp[i][j]=min(dp[i][j],dp[i+1][k-1]+dp[k][j]);
          }
        }
      }
    }
   printf("Case %d: %d\n",cnt,dp[1][n]);
  }
  return 0;
}

 

 版本2 :50ms

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn=201;
int a[maxn];
int dp[maxn][maxn];
int main()
{
  int t,cnt=0;
  cin>>t;
  while(t--)
  {
    cnt++;
    int n;
    cin>>n;
    for(int i=1;i<=n;i++)
    {
      cin>>a[i];
    }
    for(int i=0;i<n;i++)
    {
      dp[i][i]=1;
    }
    for(int d=1;d<=n;d++)//问题规模
    {
      for(int i=1;i<=n-d+1;i++)//枚举起点
      {
        int j=i+d-1;//终点
        dp[i][j]=dp[i+1][j]+1;
        for(int k=i+1;k<=j;k++)
        {
          if(a[i]==a[k])
             dp[i][j]=min(dp[i][j],dp[i+1][k-1]+dp[k][j]);
        }
      }
    }
    printf("Case %d: %d\n",cnt,dp[1][n]);
  }
  return 0;
}

第五题 Palindrome subsequence

 HDU - 4632 

 随后补题解,这个题做的我感觉自己dp还没有入门,基础dp我自己能写出来,感觉分析问题的能力还是很差。这道题我根本想不到如何分析,看了题解后才恍然大悟。

首先是区间dp,那么就要想到区间dp的一般做法,枚举起点,终点,中间点。

dp[i][j]表示区间i到j的回文数目。做题之前要知道什么是回文,以及如何判断回文。i--j的前一个状态,其实也就是子问题是i+1--j和i--j-1但是这两个状态都有共同的子状态i+1--j-1所以要把i+1--j-1减掉。当str[i]==str[j]时,也就是说i和j构成回文,此时状态方程就成了dp[i][j]+dp[i+1][j-1]+1一定要记得加1,因为i和j已经构成了回文。所以要加1.还有一个需要注意的地方是i和j未匹配时。dp[i][j]=(dp[i+1][j]+dp[i][j-1]-dp[i+1][j-1]+mod)%mod。一定要记得+mod,因为前面的可能为负数,并不是说dp[i+1][j-1]比dp[i+1][j]+dp[i][j-1]大,只是都是取模后的,无法比较大小。加上才能过。 https://blog.csdn.net/L954688947/article/details/50620302

具体可以看这位大佬的博客,我是看他的,嘻嘻嘻。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn=1001;
const int mod=10007;
char str[maxn];
int dp[maxn][maxn];
int main()
{
  int t,cnt=0;
  cin>>t;
  while(t--)
  {
    cnt++;
    memset(dp,0,sizeof(dp));
    cin>>str;
    int len=strlen(str);
    for(int i=0;i<len;i++)
    {
      dp[i][i]=1;
    }
    for(int j=0;j<=len-1;j++)
    {
      for(int i=j-1;i>=0;i--)
      {
        dp[i][j]=(dp[i+1][j]+dp[i][j-1]-dp[i+1][j-1]+mod)%mod;
        //直接取模后可能是负数,所以要加mod
        //不是说dp[i+1][j-1]比dp[i+1][j]+dp[i][j-1]大
        //是因为三个都是取模后的比不了大小
        if(str[i]==str[j])//两个字母相同,可以组成回文
        {
          dp[i][j]=(dp[i][j]+dp[i+1][j-1]+1)%mod;
        }
      }
    }
   printf("Case %d: %d\n",cnt,dp[0][len-1]);
  }
  return 0;
}

H - String painter 

 HDU - 2476 

这道题样例看懂了一切好说。状态规划方程不难推导。一般求区间最优解就是区间DP。 正常情况下,dp[i][j]=dp[i+1][j]+1。如果说str2[i]=str2[k]的时候,dp[i][j]=min(dp[i][j],dp[i+1][k]+dp[k+1][j])。注意ans[maxn]数组的用法,首先ans[i]初始化,接下来,如果对应位置相等的话,这个位置不刷。否则的话 ans[i]=min(ans[i],ans[j]+dp[j+1][i]),dp[i][j]表示a中i到j段变成b需要的操作次数。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=105;
char str1[maxn];
char str2[maxn];
int dp[maxn][maxn];//a中i到j段变成b需的操作次数
int ans[maxn];
int main()
{
  while(scanf("%s%s",str1+1,str2+1)!=EOF)
  {
    memset(dp,0,sizeof(dp));
    memset(ans,0,sizeof(ans));
    int len1=strlen(str1+1);
    for(int d=1;d<=len1;d++)
    {
      for(int i=1;i<=len1-d+1;i++)
      {
        int j=i+d-1;
        dp[i][j]=dp[i+1][j]+1;
        for(int k=i+1;k<=j;k++)
        {
          if(str2[i]==str2[k])
          dp[i][j]=min(dp[i][j],dp[i+1][k]+dp[k+1][j]);
        }
      }
    }
    for(int i=1;i<=len1;i++)
    {
      ans[i]=dp[1][i];
    }
    for(int i=1;i<=len1;i++)
    {
      if(str1[i]==str2[i])
      {
        ans[i]=ans[i-1];//对应位置相等,这个位置不刷
      }
      else
      {
        for(int j=1;j<=i;j++)
        {
          ans[i]=min(ans[i],ans[j]+dp[j+1][i]);
        }
      }
    }
    cout<<ans[len1]<<endl;
  }
  return 0;
}

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值