区间DP()

区间DP

1.区间DP简介

区间型动态规划是线性动态规划的拓展,它将区间长度
作为阶段,长区间的答案与短区间有关。

在求解长区间答案前需先将短区间答案求出。

区间型动态规划的典型应用有石子合并、乘积最大等。

2.经典例题:石子合并

题目描述

在一个操场上一排地摆放着 \(N\) 堆石子。现要将石子有次序地合并成一堆。规定每次只能选相邻的 \(2\) 堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的得分。

计算出将 \(N\) 堆石子合并成一堆的最小得分。

输入

第一行为一个正整数 \(N\);

以下 N 行,每行一个正整数,分别表示第 \(i\) 堆石子的个数 \((1≤i≤N)\)。

输出

一个正整数,即最小得分。

输入样例

7
13
7
8
16
21
4
18

输出样例

239

数据范围

\(2\leq N\leq 100\)
\(0\leq\) 每堆石子的数量 \(\leq 10000\)

注:本题来自信息学奥赛一本通第1274题

3.经典例题思路

还是一样,我们先来设 \(f\) 数组:

所有将第 \(i\) 堆石子到第 \(j\) 堆石子合并成一堆的最小合并方式

在状态计算方面,我们可以把每一堆石子分成两个区间。

每次都枚举一个 \(k\),左区间有 \(1\) 个元素,右区间就有 \(k-1\) 个元素;左区间有 \(2\) 个元素,右区间就有 \(k-2\) 个元素,以此类推……

通过这一点,我们就可以用左区间的最小得分加上右区间的最小得分,再加上合并左区间和右区间的得分来推出每个区间的最小值。

所以,状态转移方程为:

注1:\(k\) 是从 \(i\) 枚举到 \(j-1\) 的

注2:此处 \(s\) 数组是前缀和数组,即 \(s[i]\) 为 开始元素一直加到第 \(i\) 个元素的值

4.经典例题代码(注释已全部标出)

#include<iostream>
using namespace std;
const int N=305;
int n;
int s[N];
int f[N][N];
int main(){
    scanf("%d",&n); 
    for(int i=1;i<=n;i++) scanf("%d",&s[i]);
    for(int i=1;i<=n;i++) s[i]+=s[i-1];//求前缀和 
    for(int len=2;len<=n;len++)//要枚举的区间长度,因为len=1的情况结果为0,所以代码中不用体现 
        for(int i=1;i+len-1<=n;i++){//左区间位置 
            int l=i,r=i+len-1;//区间的左端和右端 
            f[l][r]=1e8;//每次都要初始化,要不然就全是0 
            for(int k=l;k<r;k++)
                f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]+s[r]-s[l-1]);//状态转移方程 
        }
    printf("%d",f[1][n]);
    return 0;
}

完awa~

如果觉得还行,就点个赞吧,您的支持对我来说很重要,谢谢。

————————

区间DP

1.区间DP简介

区间型动态规划是线性动态规划的拓展,它将区间长度
作为阶段,长区间的答案与短区间有关。

在求解长区间答案前需先将短区间答案求出。

区间型动态规划的典型应用有石子合并、乘积最大等。

2.经典例题:石子合并

题目描述

在一个操场上一排地摆放着 \(N\) 堆石子。现要将石子有次序地合并成一堆。规定每次只能选相邻的 \(2\) 堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的得分。

计算出将 \(N\) 堆石子合并成一堆的最小得分。

输入

第一行为一个正整数 \(N\);

以下 N 行,每行一个正整数,分别表示第 \(i\) 堆石子的个数 \((1≤i≤N)\)。

输出

一个正整数,即最小得分。

输入样例

7
13
7
8
16
21
4
18

输出样例

239

数据范围

\(2\leq N\leq 100\)
\(0\leq\) 每堆石子的数量 \(\leq 10000\)

注:本题来自信息学奥赛一本通第1274题

3.经典例题思路

还是一样,我们先来设 \(f\) 数组:

所有将第 \(i\) 堆石子到第 \(j\) 堆石子合并成一堆的最小合并方式

在状态计算方面,我们可以把每一堆石子分成两个区间。

每次都枚举一个 \(k\),左区间有 \(1\) 个元素,右区间就有 \(k-1\) 个元素;左区间有 \(2\) 个元素,右区间就有 \(k-2\) 个元素,以此类推……

通过这一点,我们就可以用左区间的最小得分加上右区间的最小得分,再加上合并左区间和右区间的得分来推出每个区间的最小值。

所以,状态转移方程为:

注1:\(k\) 是从 \(i\) 枚举到 \(j-1\) 的

注2:此处 \(s\) 数组是前缀和数组,即 \(s[i]\) 为 开始元素一直加到第 \(i\) 个元素的值

4.经典例题代码(注释已全部标出)

#include<iostream>
using namespace std;
const int N=305;
int n;
int s[N];
int f[N][N];
int main(){
    scanf("%d",&n); 
    for(int i=1;i<=n;i++) scanf("%d",&s[i]);
    for(int i=1;i<=n;i++) s[i]+=s[i-1];//求前缀和 
    for(int len=2;len<=n;len++)//要枚举的区间长度,因为len=1的情况结果为0,所以代码中不用体现 
        for(int i=1;i+len-1<=n;i++){//左区间位置 
            int l=i,r=i+len-1;//区间的左端和右端 
            f[l][r]=1e8;//每次都要初始化,要不然就全是0 
            for(int k=l;k<r;k++)
                f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]+s[r]-s[l-1]);//状态转移方程 
        }
    printf("%d",f[1][n]);
    return 0;
}

完awa~

如果觉得还行,就点个赞吧,您的支持对我来说很重要,谢谢。