区间DP

石子合并

方法:区间DP

想法

首先想到贪心

但是由于只能合并相邻的石子,因此不能做到极致地贪最小,换句话说,合并哪个是不确定的,所以不能用贪心

还有什么方法?

DFS枚举每次合并?

但是时间复杂度不允许,因此我们考虑DP

如何DP?

1650627688004

状态转移方程?

考虑l到r以k为分割点的所有的合并方法

i到k的最小代价 + k+1到j的最小代价 + i到j的区间和

得出状态转移方程:

\(f(i, j) = min{f(i, k) + f(k+1, j) + s[j] - s[i - 1], f(i, j)}\)

答案如何表示?

答案:1到n石子合并的最小代价

根据集合的定义可知:f[1, n]即为答案

注意

要通过枚举区间长度len来确定l和r

原因: 更长的区间状态需要从更短的区间转移过来,所以需要先枚举更短的区间

完整代码

#include <iostream>
#include <cstring>
using namespace std;
const int N = 310;

int s[N], f[N][N], n;

int main()
{
    scanf("%d", &n);
    memset(f, 0x3f3f3f3f, sizeof f); // 由于要求的是最小值,所以初始化INF是正的
    for(int i = 1; i <= n; i++)
    {
        scanf("%d", &s[i]);
        s[i] += s[i-1]; //前缀和
        f[i][i] = 0; // 长度是1的区间代价自然是0,因为不用合并
    }
    for(int len = 2; len <= n; len++) // 从2开始,长度为1的区间已经确定
    {
        for(int l = 1; l + len - 1 <= n; l++) // 区间左
        {
            int r = l + len - 1; // 知道l和len,r也就确定了
            for(int k = l; k < r; k++) // 中间点
                f[l][r] = min(f[l][r], s[r] - s[l - 1] + f[l][k] + f[k+1][r]);
        }
    }
    printf("%d", f[1][n]);
    return 0;
}

环状区间DP

【一本通 5.1 例 1】石子合并

题目描述

\(n\) 堆石子绕圆形操场排放,现要将石子有序地合并成一堆。规定每次只能选相邻的两堆合并成新的一堆,并将新的一堆的石子数记做该次合并的得分。

请编写一个程序,读入堆数 \(n(1≤n≤200)\)。 及每堆的石子数,并进行如下计算:

1、选择一种合并石子的方案,使得做 \(n-1\) 次合并得分总和最大。

2、选择一种合并石子的方案,使得做 \(n-1\) 次合并得分总和最小。

输入

输入第一行一个整数 \(n\),表示有 \(n\) 堆石子。

第二行 \(n\) 个整数,表示每堆石子的数量。

输出

输出共两行:

第一行为合并得分总和最小值,

第二行为合并得分总和最大值。

基本思路

环状区间DP其实与普通的区间DP并无太大区别,唯一的差别在于其从线性的变成了环状的,其特点在于可以循环取数对于这种情况
对于这种情况,我们可以断环成链

将记录石子数量的数组\(a[N]\)的范围倍长,变为\(a[2N]\),而后将\(a[1\sim n]\)映射至\(a[n+1 \sim 2n]\),仔细观察发现此时数组也具备环的属性,直接套普通的区间DP即可

#include <iostream>
#include <cstring>
#include <algorithm>
#define INF 0x3f3f3f3f

using namespace std;

const int N = 210;
typedef long long LL;

int a[N], s[2 * N], f1[N][N], f2[N][N];

int main()
{
    int n;
    cin >> n;
    for(int i = 1; i <= n; i ++)
    {
        cin >> a[i];
        a[i + n] = a[i]; // 断环成链!
    }

    for(int i = 1; i <= 2 * n; i ++)
        s[i] = s[i - 1] + a[i]; // 计算前缀和数组
		
    for(int len = 2; len <= n; len ++)
    {
        for(int i = 1; i + len - 1 <= 2 * n; i ++)
        {
            int j = i + len - 1;
            f1[i][j] = 0; // max
            f2[i][j] = INF; // min
            for(int k = i; k < j; k ++)
            {
                f1[i][j] = max(f1[i][j], f1[i][k] + f1[k + 1][j] + s[j] - s[i - 1]);
                f2[i][j] = min(f2[i][j], f2[i][k] + f2[k + 1][j] + s[j] - s[i - 1]);
            }
        }
    }

    int mx = -INF, mn = INF;
    for(int i = 1; i <= n; i ++)
    {
        mx = max(mx, f1[i][i + n - 1]);
        mn = min(mn, f2[i][i + n - 1]);
    }
    cout << mn << endl << mx << endl;
    return 0;
}

拓展例题

[NOIP2006 提高组]能量项链

\(\color{green} AC 代码\)

#include <iostream>
#include <cstring>
#include <algorithm>
#define INF 0x3f3f3f3f

using namespace std;

const int N = 1100;
typedef long long LL;

int a[2 * N], f[N][N]; 

int main()
{
    int n;
    cin >> n;
    for(int i = 1; i <= n; i ++)
    {
        cin >> a[i];
        a[i + n] = a[i];
    }    

    for(int len = 2; len <= n + 1; len ++)
    {
        for(int i = 1; i + len - 1 <= 2 * n; i++)
        {
            int j = i + len - 1;
            for(int k = i + 1; k <= j - 1; k ++)
            {
                f[i][j] = max(f[i][j], f[i][k] + f[k][j] + a[i] * a[j] * a[k]);
            }
        }
    }

    int mx = -INF;
    for(int i = 1; i <= n; i++)
    {
        mx = max(mx, f[i][i + n]);
    }

    cout << mx << endl;
    
    return 0;
}
posted @ 2022-07-23 22:30  MoyouSayuki  阅读(24)  评论(0编辑  收藏  举报
:name :name