动态规划:区间动态规划

16 篇文章 0 订阅
2 篇文章 0 订阅

区间动态规划

石子合并

题面

n n n堆石子排成一排,第 i i i堆石子有 a i a_i ai颗,每次我们可以选择相邻的两堆石子合并,代价是两堆石子数目的和,现在我们要一直合并这些石子,使得最后只剩下一堆石子,问总代价最少是多少?

思路

考虑最后合并的两堆石子,存在一个分界线x,其中一堆是第1堆到第x堆石子合并得到的结果,另一堆是第x+1堆石子到第n堆石子合并得到的结果;

当x确定的时候,总代价=合并第1堆石子到第x堆石子的代价+合并第x+1堆石子到第n堆石子的代价+总石子数;

对x的位置进行枚举;

递归

#include <bits/stdc++.h>
using namespace std;
const int N = 510;
int n, a[N], s[N];
int solve(int l, int r)
{
    if (l == r)
        return 0;
    int ans = 1 << 30;
    for (int m = l; m < r; m++)
        ans = min(ans, solve(l, m) + solve(m + 1, r));
    return ans + s[r] - s[l - 1];
}
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];
    printf("%d", solve(1, n));
    return 0;
}

记忆化搜索

#include <bits/stdc++.h>
using namespace std;
const int N = 510;
int n, a[N], s[N], f[N][N];
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);
    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, -1, sizeof f);
    printf("%d", solve(1, n));
    return 0;
}

动态规划

从小区间开始逐渐扩散到大区间。

#include <bits/stdc++.h>
using namespace std;
const int N = 510;
int n, a[N], s[N], f[N][N];
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; j++)        //区间的左端点
            for (int k = j; k < j + i; k++) //枚举分界线
                f[j][j + i] = min(f[j][j + i], f[j][k] + f[k + 1][j + i] + s[j + i] - s[j - 1]);
    printf("%d", f[1][n]);
    return 0;
}

括号序列

题面

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

合法的括号序列的定义是:

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

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

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

在区间 [ i , j ] [i, j] [i,j] s i s i + 1 . . . s j s_is_{i+1}...s_j sisi+1...sj)中最长合法子序列长什么样,有4种可能情况:

  1. s i s i + 1 . . . s j s_is_{i+1}...s_j sisi+1...sj的最长合法子序列等于 s i + 1 . . . s j s_{i+1}...s_j si+1...sj的最长合法子序列,也就是说最左边的括号 s i s_i si不在当前的最长合法子序列中,问题变成了考虑区间 [ i + 1 , j ] [i + 1, j] [i+1,j]时的情况;
  2. s i s i + 1 . . . s j s_is_{i+1}...s_j sisi+1...sj的最长合法子序列等于 s i s i + 1 . . . s j − 1 s_is_{i+1}...s_{j−1} sisi+1...sj1的最长合法子序列,也就是说最右边的括号 s j s_j sj不在当前的最长合法子序列中,问题变成了考虑区间 [ i , j − 1 ] [i, j - 1] [i,j1]时的情况;
  3. 如果 s i s_i si s j s_j sj匹配( s i s_i si 是左括号, s j s_j sj是右括号,并且它们属于同一种括号类型),那么 s i A s j s_iAs_j siAsj也是潜在的 s i s i + 1 . . . s j s_is_{i+1}...s_j sisi+1...sj的最长合法子序列,其中 A A A表示 s i + 1 . . . s j − 1 s_{i+1}...s_{j−1} si+1...sj1的最长合法子序列,问题变成了考虑区间 [ i + 1 , j − 1 ] [i + 1, j - 1] [i+1,j1]时的情况;
  4. 存在一个分界线 k k k s i . . . s k , s k + 1 . . . s j s_i...s_k,s_{k+1}...s_j si...sk,sk+1...sj的最长合法子序列分别是 A A A, B B B,那么 A B AB AB也是潜在的 s i s i + 1 . . . s j s_is_{i+1}...s_j sisi+1...sj的最长合法子序列,问题变成了考虑区间 [ i , k ] [i, k] [i,k] [ k + 1 , j ] [k + 1, j] [k+1,j]时的情况;
#include <bits/stdc++.h>
using namespace std;
const int N = 510;
int n, f[N][N];
char str[N];
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\n", f[1][n]);
    return 0;
}

石子合并2

题面

n n n堆石子围成一个圈,第 i i i堆石子有 a i a_i ai颗,每次我们可以选择相邻的两堆石子合并,代价是两堆石子数目的和,现在我们要一直合并这些石子,使得最后只剩下一堆石子,问总代价最少是多少?

暴力

考虑一开始的圆,圆上有 n 堆石子,在相邻的石子间连边,一共有 n 条边,每次合并相邻两堆石子的时候,有一条边会消失。从开始到结束一共要合并 n - 1 次,有 n - 1 条边会消失,也就是说最后一定会有一条边没有消失;

我们可以理解成这条边一开始就不存在,现在只剩下了 n - 1 条边,问题变成了一条链的情况。

倍增

构造一个长度为 2n 的序列,它是由两个 a 数组接起来得到的。也就是说,对于前 n 个位置中的位置 i,对应了原来的第 i 堆石子,对于后 n 个位置中的位置 i,对应了原来的第 i - n 堆石子。

  • 假如消失的是第 n 堆和第 1 堆石子之间的边,是不是变成了一条 1, 2, …, n 的链,最小代价为 f[1][n]
  • 假如消失的是第 1 堆和第 2 堆石子之间的边,是不是变成了一条 2,…, n, 1 的链,相当于现在序列中的第 2 到第 n + 1 个位置,最小代价为 f[2][n + 1]
  • 假如消失的是第 2 堆和第 3 堆石子之间的边,是不是变成了一条 3,…, n, 1, 2 的链,相当于现在序列中的第 3 到第 n + 2 个位置,最小代价为 f[3][n + 2]
  • 假如消失的是第 n - 1 堆和第 n 堆石子之间的边,是不是变成了一条 n, 1, …, n - 1 的链,相当于现在序列中的第 n 到第 2n - 1 个位置,最小代价为f[n][2n - 1]

我们只要在这 n 种情况中取最小值就可以啦!

#include <bits/stdc++.h>
using namespace std;
const int N = 260;
int n, a[2 * N], f[2 * N][2 * N], s[2 * N];

int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
    {
        scanf("%d", &a[i]);
        a[n + i] = a[i];
    }
    for (int i = 1; i <= 2 * n; i++)
        s[i] = s[i - 1] + a[i];
    memset(f, 127, sizeof f);
    for (int i = 1; i <= 2 * n; i++)
        f[i][i] = 0;
    for (int l = 1; l < 2 * n; l++)
        for (int i = 1; i <= 2 * n - l; i++)
        {
            int j = i + l;
            for (int k = i; k < j; k++)
                f[i][j] = min(f[i][j], f[i][k] + f[k + 1][j] + s[j] - s[i - 1]);
        }
    int ans = 1 << 30;
    for (int i = 1; i <= n; i++)
        ans = min(ans, f[i][i + n - 1]);
    printf("%d\n", ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值