【专题】区间dp

1.【nyoj737】石子合并

传送门:点击打开链接

描述     有N堆石子排成一排,每堆石子有一定的数量。现要将N堆石子并成为一堆。合并的过程只能每次将相邻的两堆石子堆成一堆,每次合并花费的代价为这两堆石子的和,经过N-1次合并后成为一堆。求出总的代价最小值。
输入
有多组测试数据,输入到文件结束。
每组测试数据第一行有一个整数n,表示有n堆石子。
接下来的一行有n(0< n <200)个数,分别表示这n堆石子的数目,用空格隔开
输出
输出总代价的最小值,占单独的一行
样例输入
3
1 2 3
7
13 7 8 16 21 4 18
样例输出
9
239
思路:

dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+sum[i][j]),dp[i][j]代表从i到j合并的最小值,sum[i][j]代表从i到j的价值和。

区间dp的循环是从小区间到大区间,根据这一规律写出循环结构。

注意dp[i][i]是0。

代码:

#include<bits/stdc++.h>
using namespace std;
int dp[205][205],a[205],sum[205][205];
int main()
{
    int n,i,j,k;
    while(cin>>n)
    {
        memset(sum,0,sizeof(sum));
        memset(dp,0x3f3f3f3f,sizeof(dp));
        for(i=1; i<=n; i++)
        {
            scanf("%d",&a[i]);
            dp[i][i]=0;
            sum[i][i]=a[i];
        }
        for(i=1; i<n; i++)
        {
            for(j=i+1; j<=n; j++)
                sum[i][j]+=sum[i][j-1]+a[j];
        }
        for(int len=2; len<=n; len++)
            for(i=1; i<=n-len+1; i++)
            {
                j=i+len-1;
                for(k=i; k+1<=j; k++)
                    dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+sum[i][j]);
            }
        printf("%d\n",dp[1][n]);
    }
    return 0;
}

2.【nyoj37】回文字符串

传送门:点击打开链接

给你一个字符串,可在任意位置添加字符,最少再添加几个字符,可以使这个字符串成为回文字符串。

样例输入
1
Ab3bd
样例输出
2
思路1:

利用反串找LCA,相同题目

#include<bits/stdc++.h>
using namespace std;
char s[1005],s1[1005];
int dp[1005][1005];
int LCA(char *s,char *s1,int n)
{
    int i,j;
    for(i=0; i<n; i++)
        for(j=0; j<n; j++)
        {
            if(s[i]==s1[j])
                dp[i+1][j+1]=dp[i][j]+1;
            else
                dp[i+1][j+1]=max(dp[i+1][j],dp[i][j+1]);
        }
    return dp[n][n];
}
int main()
{
    int t,i;
    cin>>t;
    while(t--)
    {
        scanf("%s",s);
        int len=strlen(s);
        for(i=0; i<len; i++)
            s1[i]=s[len-1-i];
        int n=LCA(s,s1,len);
        printf("%d\n",len-n);
    }
}
思路2:

s[i]=s[j],dp[i][j]=dp[i+1][j-1];若!=,dp[i][j]=min(dp[i+1][j]+1,dp[i][j-1]+1)

注意dp[i][i]=0,当len=2时,若s[i]=s[j]则dp[i][j]=0,所以初始化dp[i][i-1]也要全部赋为0

#include<bits/stdc++.h>
using namespace std;
char s[1005];
int dp[1005][1005];
int main()
{
    int t,i,j;
    cin>>t;
    while(t--)
    {
        scanf("%s",s);
        int n=strlen(s);
        memset(dp,0x3f3f3f3f,sizeof(dp));
        dp[0][0]=0;
        for(i=1; i<n; i++)
            dp[i][i]=dp[i][i-1]=0;
        for(int len=2; len<=n; len++)
            for(i=0; i<=n-len; i++)
            {
                j=i+len-1;
                if(s[i]==s[j]) dp[i][j]=dp[i+1][j-1];
                else dp[i][j]=min(dp[i+1][j]+1,dp[i][j-1]+1);
            }
        printf("%d\n",dp[0][n-1]);
    }
}

3.【nyoj15】括号匹配(二)

传送门:点击打开链接

描述 给你一个字符串,里面只包含"(",")","[","]"四种符号,请问你需要至少添加多少个括号才能使这些括号匹配起来。
如:
[]是匹配的
([])[]是匹配的
((]是不匹配的
([)]是不匹配的
输入
第一行输入一个正整数N,表示测试数据组数(N<=10)
每组测试数据都只有一行,是一个字符串S,S中只包含以上所说的四种字符,S的长度不超过100
输出
对于每组测试数据都输出一个正整数,表示最少需要添加的括号的数量。每组测试输出占一行
样例输入
4
[]
([])[]
((]
([)]
样例输出
0
0
3
2
思路: 这道题并不是让它变成回文(第二个例子),这是本题与上题最本质的区别。dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]),特殊情况当s[i]与s[j]匹配时,dp[i][j]=min(dp[i][j],dp[i+1][j-1]) 注意当截取长度为2且s[i]与s[j]匹配时,不需要添加符号,所以初始化dp[i][i-1]=0,dp[i][i]=1。
#include<bits/stdc++.h>
using namespace std;
char s[105];
int dp[105][105];
int main()
{
    int t,i,j,k;
    cin>>t;
    while(t--)
    {
        scanf("%s",s);
        int n=strlen(s);
        memset(dp,0x3f3f3f3f,sizeof(dp));
        dp[0][0]=1;
        for(i=1; i<n; i++)
        {
            dp[i][i]=1;
            dp[i][i-1]=0;
        }
        for(int len=2; len<=n; len++)
            for(i=0; i<=n-len; i++)
            {
                j=i+len-1;
                if(s[i]=='['&&s[j]==']'||s[i]=='('&&s[j]==')')
                    dp[i][j]=dp[i+1][j-1];
                for(k=i; k+1<=j; k++)//这里不是else
                    dp[i][j]=min(dp[i][k]+dp[k+1][j],dp[i][j]);
            }
        printf("%d\n",dp[0][n-1]);
    }
}

4.【nyoj746】整数划分(四)

传送门:点击打开链接

给出两个整数 n , m ,要求在 n 中加入m - 1 个乘号,将n分成m段,求出这m段的最大乘积

输入
第一行是一个整数T,表示有T组测试数据
接下来T行,每行有两个正整数 n,m ( 1<= n < 10^19, 0 < m <= n的位数);
输出
输出每组测试样例结果为一个整数占一行
样例输入
2
111 2
1111 2
样例输出
11
121

思路:

由于最大的划分所有的乘号都是固定的,可以从前到后以此找出所有乘号的位置,找出最优子结构(从结论入手向前拆分问题)。

dp[i][j]代表0到i个数字中添加j个乘号,要求dp[n][m]即求dp[k][m-1]*a[k+1][i],应用到所有dp[i][j]=max(dp[i][j],dp[k][j-1]*a[k+1][i]),a[i][j]是从i到j的组合数。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
LL dp[25][25],a[25][25];
char s[25];
int main()
{
    LL n,m,t,i,j,k;
    cin>>t;
    while(t--)
    {
        scanf("%s%lld",s,&m);
        n=strlen(s);
        memset(dp,0,sizeof(dp));
        for(i=0; i<n; i++)
            a[i][i]=s[i]-'0';
        for(i=0; i<n-1; i++)
            for(j=i+1; j<n; j++)
                a[i][j]=a[i][j-1]*10+(s[j]-'0');
        for(i=0; i<n; i++)
            dp[i][0]=a[0][i];
        for(j=1; j<=m-1; j++)
            for(i=1; i<n; i++)
                for(k=0; k+1<=i; k++)
                    dp[i][j]=max(dp[i][j],dp[k][j-1]*a[k+1][i]);
        printf("%lld\n",dp[n-1][m-1]);
    }
    return 0;
}



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值