区间动态规划问题(DP)

石子合并

  • 有 n 堆石子排成一排,第 i 堆石子有 ai 颗,每次我们可以选择相邻的两堆石子合并,代价是两堆石子数目的和,现在我们要一直合并这些石子,使得最后只剩下一堆石子,问总代价最少是多少?
    • 输入格式:第一行一个数字 n。接下来一行 n个整数 a1,a2,…,an。
    • 输出格式:一行一个整数,表示答案。
    • 数据规模对于所有数据,保证 1≤n,ai≤500。
//递归做法
#include<bits/stdc++.h>

using namespace std;

int n, a[501], s[501], f[501][501];

int solve(int l, int r){
    if(f[l][r] != -1)
        return f[l][r];
    if(l == r)
        return f[l][r] = 0;
    int ans = 1 << 30;
    for(int m = l; m < r; m++)
        ans = min(ans, solve(l, m) + solve(m+1, r));
    return f[l][r] = ans + s[r] - s[l-1];
    //
}

int main(){
    scanf("%d", &n);
    memset(f, 255, sizeof(f));
    for(int i = 1; i <= n; i++)
        scanf("%d", &a[i]);
    for(int i = 1; i <= n; i++)
        s[i] = s[i - 1] + a[i];
    printf("%d\n", solve(1 , n)); 
}
  • 用动态规划的思路来考虑,现在我们要把所有区间[l, r]的最小代价都算出来;
  • 最优子结构:为了计算合并区间[i, j]的最小代价,我们需要先计算合并所有满足i <= k < j 的区间[ i, k ], [k + 1, j ]的最小代价;
  • 状态:用f[ i ][ j ]表示合并区间[ i ][ j ]的最小代价。
  • 转移:f[ i ][ j ] = min f[ i ] [ k ] + f[k + 1][ j ] + ax;
  • 如何保证再算一个题的解之前,实现计算了这个问题的所有子问题的解
  • 需要把区间按照j - i排序,从小到大计算即可。
//dp做法,复杂度低
#include<bits/stdc++.h>

using namespace std;

int n, a[501], s[501], f[501][501];

int main(){
    scanf("%d", &n);
    for(int i = 1; i <= n; i++)
        scanf("%d", &a[i]);
    for(int i = 1; i <= n; i++)
        s[i] = s[i-1] + a[i]; 
    memset(f, 127, sizeof(f));
    for(int i = 1; i<= n; i++)
        f[i][i] = 0;
    for(int i = 1; i < n; i++)
        for(int j = 1; j <= n-i; j++)
            for(int k = j; k < j+i; k++)   
                f[j][j+i] = min(f[j][j+i], f[j][k] + f[k+1][i+j] + s[j+i] - s[j-1]);
    printf("%d\n", f[1][n]);
}

括号序列

  • 给定一个长度为 n 的字符串 s,字符串由 (, ), [, ] 组成,问其中最长的合法子序列有多长?也就是说,我们要找到最大的 m,使得存在 i1,i2,…,im 满足 1≤i1<i2<⋯<im≤n 并且 si1si2…sim是一个合法的括号序列。
    合法的括号序列的定义是:

    • 空串是一个合法的括号序列。

    • 若 A 是一个合法的括号序列,则 (A), [A] 也是合法的括号序列。

    • 若 A, B 都是合法的括号序列,则 AB 也是合法的括号序列。

  • 输入格式:
    第一行一个整数 n。接下来一行,一个长度为 n的字符串 s。

  • 输出格式:
    一个数,表示答案。

    • 状态:用f[ i ][ j ]表示最长合法子序列的长度;
    • 转移:
      • 如果si和sj匹配,f[ i ][ j ] = max(f[ i ][ j ], f[i + 1][j - 1]+2)
      • 合并AB序列,枚举AB的分界线k, f[ i ][ j ] = max(f[ i ][ j ], f[ i ] [ k ] + f[k + 1][ j )
#include<bits/stdc++.h>

using namespace std;

int f[501][501], n;
char str[511];

int main(){
    scanf("%d%s", &n, str + 1);
    memset(f, 0, sizeof(f));
    for(int i = 1; i < n; i++)
        for(int j = 1; j <= n-i; j++){
            if(str[j] == '(' && str[j+i] == ')' || str[j] == '[' && str[j+i] == ']')
                f[j][j+i] = f[j+1][j+i-1] + 2;
            for(int k = j; k < j+i; k++)
                f[j][j+i] = max(f[j][j+i], f[j][k] + f[k+1][j+i]);
        }
    printf("%d", f[1][n]);    
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值