在定义状态的时候,与线性DP和背包问题不一样,这里是定义了一个区间
状态表示
f[i][j]
表示一个区间,第i
堆石子到第j
堆石子这个区间集合
:所有将第i
堆石子到第j
堆石子合并成一堆石子的合并方式属性
:这些合并方式中,代价最小值关键点:
先将分界线左边进行合并,再将分界线右边合并,最后再将这俩堆进行合并
(此时就需要前缀和)状态计算
左边的最小代价 + 右边的最小代价 + 最后俩堆合并成一堆的最小代价
)枚举顺序:
最后结果
f[1][n]
'序列:2 1 3 6 4'
a[i]; //代表值
s[i]; //代表前缀和
'方法1:直接输入s[i]'
for (int i = 1; i <= n; i ++) cin >> s[i];
for (int i = 1; i <= n; i ++)
{
s[i] += s[i - 1] ;
cout << s[i] << endl;
}
'方法2:直接输入a[i]'
for (int i = 1; i <= n; i ++) cin >> a[i];
for (int i = 1; i <= n; i ++) s[i] = s[i - 1] + a[i];
#include
#include
using namespace std;
const int N = 310;
int n;
int s[N]; //前缀和,进行最后一堆合并时,它的代价是所有堆的和
int f[N][N];
int main()
{
cin >> n;
for (int i = 1; i <= n; i ++) cin >> s[i];
for (int i = 1; i <= n; i ++) s[i] += s[i - 1]; //处理前缀和
//边界情况下是区间长度为1的时候,此时只有一堆,合并不需要代价0,因为f[N][N]是全局数组,所以本来就是0,
//所以直接从长度为2开始计算。
for(int len = 2; len <= n; len ++) //长度从小到大枚举所有状态
{
for (int i = 1; i + len - 1 <= n; i ++) //枚举起点(左右端点就可以计算)
{
int l = i, r = i + len - 1;
f[l][r] = 1e8; //进行初始化
for (int k = l; k < r; k ++) //枚举分界点k
{
f[l][r] = min(f[l][r], f[l][k] + f[k + 1][r] + s[r] - s[l - 1]);
}
}
}
cout << f[1][n];
return 0;
}
#include
using namespace std;
const int N=310,INF=0x3f3f3f3f;
int f[N][N];
int s[N];
int n;
int main(){
cin>>n;
memset(f,INF,sizeof(f)); //记得初始化
for(int i=1;i<=n;i++){
cin>>s[i];
f[i][i]=0; //初始化,本身自己不需要合并,所以需要的体力为0
}
for(int i=1;i<=n;i++) s[i]+=s[i-1];
for(int len=2;len<=n;len++){
for(int l=1;l+len-1<=n;l++){
int r=l+len-1;
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]);
}
}
}
cout<<f[1][n]<<endl;
return 0;
}
不好循环,要保证计算每个f[i][j]的时候,f[i][j]用到的所有状态都必须是已经计算好的,所以需要一个顺序,保证在计算的时候保证计算当前状态时,所用到的其他状态都计算好了
区间DP一般从小到大循环(先循环区间长度),在循环区间的左端点,最后枚举决策
for (int len = 1; len <= n; len++) { // 区间长度
for (int i = 1; i + len - 1 <= n; i++) { // 枚举起点
int j = i + len - 1; // 区间终点
if (len == 1) {
dp[i][j] = 初始值
continue;
}
for (int k = i; k < j; k++) { // 枚举分割点,构造状态转移方程
dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j] + w[i][j]);
}
}
}