区间DP其实是线性DP的扩展,通常是分阶段划分问题,并且与阶段中元素出现的顺序和由前一阶段的一些元素合并而来有很大的关系。令状态 f [ i ][ j ] 表示将下标位置 i 到 j 的所有元素合并能获得价值的最大值,那么k为我们枚举的分界点,cost则为将这两组元素合并起来的代价。
区间 DP 的特点:
合并:即将两个或多个部分进行整合;
特征:能将问题分解为能两两合并的形式;
求解:对整个问题设最优值,枚举合并点,将问题分解为左右两个部分,最后合并两个部分的最优值得到原问题的最优值。
我们先来看一个区间DP的入门题目;
根据我们的转移方程:
我们知道cost是我们合并两个部分的代价,那我们可以使用一个前缀和来预处理一下我们的代价;
不知道前缀和的可以来这里:差分&前缀和_是饿梦啊的博客-CSDN博客
我们来分析一下:
由于计算f [ i ][ j ] 的值时需要知道所有 f [ i ][ k ] 和 f [ k+1 ][ j ] 的值,而这两个中包含的元素的数量都小于f [ i ][ j ],所以我们以 len = i - j + 1 作为 DP 的阶段。我们从小到大枚举len ,然后枚举 i 的值,最后我们枚举 k ,时间复杂度为;
#include<iostream>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<unordered_map>
#define x first
#define y second
#define _for(i,s,t) for(int i = (s);i <=t ; i++)
#define novice ios::sync_with_stdio(false); cin.tie(0);
using namespace std;
typedef unsigned long long ULL;
typedef long long LL;
typedef pair<int,int> PII;
const int N = 1010;
int n;
LL p[N],f[N][N],a[N];
int main()
{
novice;
cin>>n;
for(int i =1 ;i<= n ;i++) cin>>a[i];
for(int i =1 ;i<= n ;i++) p[i] = p[i-1] + a[i];
for(int len = 2 ;len <= n ;len ++)//遍历长度
{
for(int i =1 ;i + len - 1<= n ;i++)//遍历结点
{
int c = i + len - 1;
f[i][c] = 1e8;
for(int j = i;j <= c; j++) //遍历分界点
{
int v = p[c] - p[i-1];
f[i][c] = min(f[i][c] , f[i][j] + f[j + 1][c] + v);
}
}
}
cout<<f[1][n]<<endl;
return 0;
}
这个题我觉得还是有难度的;
首先这是一个环,我们考虑把这个环拆成链;我们开两倍空间,将数据存储两倍;这样我们就可以把环的所有情况存起来;(注意:这个时候我们更新dp数组的时候,我们就要全部更新一遍,不能只更新1~n的值)
然后我们看看这个代价怎么算,如图;
当我们的枚举的长度len = 3 的时候,我们枚举 i 的值的时候,我们发现 k (枚举的临界点),k == i时,并不会产生价值;我们让 k 从 i + 1 开始;
我们枚举 k 时候,我们要用到 i ,k , j;
i : 以 len 为长度的第一个点;
k:枚举的分界点,前一部分的尾;
j:以len为长度的最后一个有个点的下一个点(j 的值为最后一个点的尾);
我们可以看到我们这次枚举长度时,其实是枚举的以 i 为起点,长度为 len + 1 (这个地方可以对着代码好好理解一下);
方法不唯一,也可以有其他解法;
#include<iostream>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<unordered_map>
#define x first
#define y second
#define _for(i,s,t) for(int i = (s);i <=t ; i++)
#define novice ios::sync_with_stdio(false); cin.tie(0);
using namespace std;
typedef unsigned long long ULL;
typedef long long LL;
typedef pair<int,int> PII;
const int N = 1010;
int n;
LL f[N][N],a[N];
int main()
{
novice;
cin>>n;
_for(i,1,n)
{
cin>>a[i];
a[i+n] = a[i]; // 把环转成链,存 2 * n;
}
for (int len = 2; len <= n; len++)
{
for (int i = 1; i <= 2 * n - 1; i++) //因为我们存了两倍,我们更新的时候,不能只更新 1 ~ n;
{
int j = i + len; // 表示枚举范围内的下一个;
for (int k = i + 1 ; k < j && k <= 2 * n - 1 ; k++)
{
LL v = a[i] * a[k] * a[j]; // 价值
f[i][j] = max(f[i][j], f[i][k] + f[k][j] + v);
}
}
}
LL res= 0;
for(int i =1 ;i<= n ;i++)
{
res = max(res,f[i][ i + n]);
}
cout<<res<<endl;
return 0;
}