引入
- 给定一堆长度为 的石子,每个石子有其重量 ,你需要做的是不断的合并相邻石子,每次合并两堆石子需要付出 的代价,你必须不断合并直到所有石子均被合并完成。你需要知道你付出的最小代价是多少
特点
- 区间dp的特点我们一般认为有以下
- 在某一个序列上的一个区间进行操作,如(合并)
- 可以利用子区间的最优解来构造全局的最优解
dp含义以及状态转移
状态定义:
- 区间状态,我们定义表示从位置 到位置 的最优解(最小代价,最大收益等)
- 对于每一个 我们都应该保证其状态能从子区间转移而来
状态转移:
- 区间划分:为了计算,我们往往需要一个中间值
- 将区间划分为两个区间 和
- 我们通过合并这两个子区间的代价来进行选择
- 为了能够知道所有状态的最优解,应该遍历
- 递推式
- 其中 为合并两个子区间所产生的代价
dp计算思路
- 一般区间dp有三层for循环,第一层枚举长度 ,第二次找区间起始点 ,第三层遍历所有中间点
Tip
区间dp的复杂度为
一般模板代码
我们以引入问题为例进行分析
vector<vector<int>> dp(N,vector<int>(N));
// 我们认为序列长度有n
for(int i = 1 ; i <= n ; i++){
dp[i][i] = 0;
}
// 预处理,根据题目含义做出预处理数组(前缀和,或合并代价)(此处为合并石子,即石子的前缀和,用于计算区间代价)
vector<int> vv(N);
for(int i = 1; i <= n; i++) {
vv[i] = vv[i-1] + v[i];
}
//进行区间dp,枚举所有长度n
for(int len = 2;len <= n; len++){ //第一层,枚举长度
for(int i = 1; i + len - 1 <= n; i++){ //第二层,枚举起点
int j = i + len - 1; //计算终点
dp[i][j] = INT_MAX;
for(int k = i; k < j ;k++{
dp[i][j] = min(dp[i][j],dp[i][k] + dp[k+1][j] + (vv[j] - vv[i-1]); // 区间dp转移,遍历所有中间节点,取最小值
}
}
}
cout << dp[1][n]; //答案即为从1-n的区间最优解