第八周总结(区间dp)

一.区间dp
区间DP主要是把一个大区间拆分成几个小区间,先求小区间的最优值,然后合并起来求大区间的最优值。
//一般区间DP实现代码
memset(dp, 0x3f, sizeof(dp));//0x3f  0x3f3f3f3f是常用的最大值
for (int i = 1; i <= n; i++) //区间长度为1的初始化
    dp[i][i] = 0;
for (int len = 2; len <= n; len++) //枚举区间长度
{
    for (int i = 1, j = len; j <= n; i++, j++) //区间[i,j]
    {
        //DP方程实现
    }
}
二、题目整理

题型一:石子合并问题
石子合并一条直线上有N堆石子,现在要将所有石子合并成一堆,每次只能合并相邻的两堆,合并花费为新合成的一堆石子的数量,求最小的花费。
如果
只有1堆,花费为0 
有2堆,花费为sum[2] 
有3堆,花费为min(a[1] + a[2], a[2] + a[3]) + sum[3] 
如果我们有n堆,合并的最后一次操作一定是从两堆合并成一堆.
则dp[i][j]为合并第i堆到第j堆的最小花费,把从i到j的区间分成i-k,k-j; 
DP方程为:dp[i][j] = min(dp[i][j],dp[i][k] + dp[k+1][j]) + sum[j] - sum[i-1])// (i <= k < j),sum[j]-sum[i-1]为最后合并i-j所需要的代价
 
代码
 

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
//#include<bits/stdc++.h>
using namespace std;
#define Max 0x3f
int num[1001];
int n,i,j,k,x;
    int dp[1001][1001];
int main()
{
    cin>>n;
    for(i=1;i<=n;i++)
       {
           cin>>x;
           num[i]=num[i-1]+x;
       }
       memset(dp,Max,sizeof(dp));
       for(i=1;i<=n;i++)
            dp[i][i]=0;
       for(int len=2;len<=n;len++)
       {
           for(i=1,j=len;j<=n;i++,j++)
           {
               for(k=i;k<j;k++)
               {
                   if(dp[i][j]>dp[i][k]+dp[k+1][j]+num[j]-num[i-1]);
                   dp[i][j]=dp[i][k]+dp[k+1][j]+num[j]-num[i-1];
               }
           }
       }
       cout<<dp[1][n]<<endl;
    return 0;
}

在写这个代码的时候遇到一点问题,就是开始我把数组定义的很大,编译的时候在memset(dp,Max,sizeof(dp))这里显示未定义,求大佬们解答;

题型二:括号匹配问题
处理方法 把中间某一元素特殊处理,在进行分配的时候不再考虑它
给一个括号组成的字符串,问最多能匹配多少个括号 
 像([)]这样的字符串匹配度为2,但是像()[]、[()]的字符串匹配度为4,也就是说括号具有分隔作用。

长度为1的串匹配度 0 
长度为2的串匹配度 0 或 2
对于这题,我们先用上面石子合并的解题方法写一下吧
规定dp[i][j]为合并第i到第j个字符的最大匹配度 
长度为n时,我们可以先检测a[i]和a[j]是否匹配,匹配dp[i][j] = dp[i+1][j-1] + 2     
不匹配,那我们可以按第一种模型处理,从任意位置分成两个区间
方法A:代码如下

#include<iostream>
#include<cstring>
#include<string>
#include<stdio.h>
using namespace std;
#define Max 0x3f
int i,j,k,n;
    char a[1001];
    int dp[1001][1001];
int main()
{
    cin>>n;
    memset(dp,Max,sizeof(dp));
    for(i=1;i<=n;i++)
        dp[i][i]=0;
        for(int len=2;len<=n;len++)
        while(gets(a+1))//gets输入字符串
        {
            if(a[1]=='e') break;
                memset(dp,0,sizeof(dp));
        int n=strlen(a+1);
        for(int len=2;len<=n;len++)
        {
            for(i=1,j=len;j<=n;i++,j++)
            {
                if((a[i]=='('&&a[j]==')')||(a[i]=='['&&a[j]==']'))
                    dp[i][j]=dp[i+1][j-1]+2;
                for( k=i;k<j;k++)
                    if(dp[i][j]<dp[i][k]+dp[k+1][j])
                    dp[i][j]=dp[i][k]+dp[k+1][j];
            }
        }
       cout<<dp[1][n]<<endl;
}
    return 0;
}


方法B:我们可以把[i,j]区间的字符当成由[i+1,j]在前面加个字符或[i,j-1]在后面加一个字符得来的.
这里我们只考虑[i,j]由[i+1,j]在前面加一个字符的情况 

    如果a[i+1]到a[j]没有和a[i]匹配的,
        那么dp[i][j] = dp[i+1][j] 

    如果a[k]和a[i]匹配(i < k <= j),那么
dp[i][j] = max(dp[i][j], dp[i+1][k-1] + dp[k+1][j] + 2); 

比如:[xxxxx]yyyyy通过括号分成两个子串

#include<iostream>
#include<cstring>
#include<string>
#include<stdio.h>
#include<cstdio>
using namespace std;
#define Max 0x3f
int i,j,k,n;
    char a[1001];
    int dp[1001][1001];
int main()
{
    cin>>n;
    memset(dp,Max,sizeof(dp));
    for(i=1;i<=n;i++)
        dp[i][i]=0;
        for(int len=2;len<=n;len++)
        while (gets(a+1))
{
    if(a[1] == 'e') break;
    memset(dp, 0, sizeof(dp));
    int n = strlen(a+1);
    for (int len = 2; len <= n; len++)
    {
        for(int i = 1, j = len; j <= n; i++, j++)
        {
            dp[i][j] = dp[i+1][j];
            for (int k = i; k <= j; k++)
                if((a[i]=='('&&a[k]==')') || (a[i]=='['&&a[k]==']'))
                    dp[i][j] = max(dp[i][j], dp[i+1][k-1] + dp[k+1][j] + 2);
        }
    }
    printf("%d\n",dp[1][n]);
}
return 0;
}


题型三:这种模型不需要枚举区间k∈[i,j],这种类型只和左右边界相关。

n个字符组成长度为m的字符串,给出增删字符的花费,可在字符串任意位置增删字符,求把字符串修改成回文串的最小花费。 
规定dp[i][j]为将[i,j]区间改成回文串的最小花费,可以看成有回文串 
1.对i端操作  [i+1,j]在前面加或者删除一个字符 
2.对j端操作  [i,j-1]在后面加或者删除一个字符 
当a[i] == a[j]时,加个dp[i+1][j-1]的情况就好了

代码

#include<iostream>
#include<cstring>
#include<string>
#include<stdio.h>
#include<cstdio>
using namespace std;

const int MAXN = 2005;
char a[MAXN], ch;
int dp[MAXN][MAXN];
int add[30],sub[30];
int main()
{
    int n, m;
    scanf("%d %d", &n, &m);
    scanf("%s", a+1);
    for (int i = 1; i <= n; i++)
    {
        scanf(" %c", &ch);
        scanf("%d %d", &add[ch-'a'], &sub[ch-'a']);
    }
    memset(dp, 0x3f, sizeof(dp));
    for (int i = 1; i <= m; i++)
        dp[i][i] = 0;
    for (int len = 2; len <= m; len++)
        for(int i = 1, j = len; j <= m; i++, j++)
        {
            dp[i][j] = min(dp[i][j], min(add[a[i]-'a'],sub[a[i]-'a']) + dp[i+1][j]);
            dp[i][j] = min(dp[i][j], dp[i][j-1] + min(add[a[j]-'a'],sub[a[j]-'a']));
            if (a[i] == a[j])
            {
                if (len==2)
                    dp[i][j] = 0;
                else
                    dp[i][j] = min(dp[i][j], dp[i+1][j-1]);
            }

}
     printf("%d\n", dp[1][m]);
    return 0;
}


题目整理
1
有一些食物,放在一个两端开口的仓库里,每天只能从两端选择一端取出一件食物,并且食物的价值是随着天数逐天递增,第i天的价值 本来价值*i,求n天取出食物所取得的最大价值。

#include<iostream>
#include<stdlib.h>
#include<algorithm>
#include<bits/stdc++.h>
using namespace std;
int dp[2001][2001];
int a[2001];
int Max(int a,int b)
{
    if(a>b)
        return a;
        return b;
}

int main()
{
    int n;
    int i,j;
    while(scanf("%d",&n)!=EOF)//用一次这种格式,还有点不适应
      {
        memset(dp,0,sizeof(dp));
        memset(a,0,sizeof(a));
        for(i=1;i<=n;i++)
            {scanf("%d",&a[i]);
         dp[i][i]=a[i]*n;}
        for(int k=1;k<n;k++)
          for(int i=1;i+k<=n;i++)
          {
             j=i+k;
            dp[i][j]=max(dp[i+1][j]+(n-k)*a[i],dp[i][j-1]+(n-k)*a[j]);
          }
        printf("%d\n",dp[1][n]);
     }
return 0;
}


2Gappu has a very busy weekend ahead of him. Because, next weekend is Halloween, and he is planning to attend as many parties as he can. Since it's Halloween, these parties are all costume parties, Gappu always selects his costumes in such a way that it blends with his friends, that is, when he is attending the party, arranged by his comic-book-fan friends, he will go with the costume of Superman, but when the party is arranged contest-buddies, he would go with the costume of 'Chinese Postman'.

Since he is going to attend a number of parties on the Halloween night, and wear costumes accordingly, he will be changing his costumes a number of times. So, to make things a little easier, he may put on costumes one over another (that is he may wear the uniform for the postman, over the superman costume). Before each party he can take off some of the costumes, or wear a new one. That is, if he is wearing the Postman uniform over the Superman costume, and wants to go to a party in Superman costume, he can take off the Postman uniform, or he can wear a new Superman uniform. But, keep in mind that, Gappu doesn't like to wear dresses without cleaning them first, so, after taking off the Postman uniform, he cannot use that again in the Halloween night, if he needs the Postman costume again, he will have to use a new one. He can take off any number of costumes, and if he takes off k of the costumes, that will be the last k ones (e.g. if he wears costume A before costume B, to take off A, first he has to remove B).

Given the parties and the costumes, find the minimum number of costumes Gappu will need in the Halloween night.

Input
Input starts with an integer T (≤ 200), denoting the number of test cases.

Each case starts with a line containing an integer N (1 ≤ N ≤ 100) denoting the number of parties. Next line contains N integers, where the ith integer ci (1 ≤ ci ≤ 100) denotes the costume he will be wearing in party i. He will attend party 1 first, then party 2, and so on.

Output
For each case, print the case number and the minimum number of required costumes.

Sample Input
2

4

1 2 1 2

7

1 2 1 1 3 2 1

Sample Output
Case 1: 3

Case 2: 4

哎呀,对英文题目还是很头疼。第一眼就不想做的感觉,哈哈,慢慢看一下,读懂题目

题意大概就是给定聚会和服饰,找到加普在万圣节之夜所需的最少服饰。一个人可以穿多件,脱掉后再去参见另一个聚会,但脱掉之后不能再穿

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
#define INF 0x3f3f
int dp[101][101];
int a[101];
int main(){

    int x;
    cin>>x;
        int t = 1;
    while(x--){
        int n;
        cin>>n;
        memset(dp,0,sizeof(dp));
        for(int i = 1; i <= n; i++)
            cin>>a[i];
        for(int i = 1; i <= n; i++)
            dp[i][i] = 1;
        for(int i = 2; i <= n; i++){
            for(int j = 1; j <= n; j++){
                dp[j][j+i-1] = 1 + dp[j][j+i-2];
                int temp = INF;
                int b = j+i-1;
                for(int k = b -1; k >= j; k--){
                    if(a[k] == a[b])
                        temp = min(temp,dp[j][k-1]+dp[k][b-1]);
                }
                dp[j][j+i-1] = min(temp,dp[j][j+i-1]);
            }
        }
           printf("Case %d: %d\n",t++,dp[1][n]);
    }
return 0;
}


这题目刚刚在提交的时候出现了很多问题,我发现好像用C语言交更容易一些吧

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

季沐晴

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值