区间DP详细解析
1.定义与性质
区间类动态规划是线性动态规划的扩展,它在分阶段地划分问题时,与阶段中元素出现的顺序和由前一阶段的哪些元素合并而来有很大的关系。
令状态
区间
-
1.合并:即将两个或多个部分进行整合,当然也可以反过来;
-
2.特征:能将问题分解为能两两合并的形式;
-
3.求解:对整个问题设最优值,枚举合并点,将问题分解为左右两个部分,最后合并两个部分的最优值得到原问题的最优值。
2.模板题
在一条链上有
思路
令
写出状态转移方程:
然后我们用前缀和
由于计算
首先从小到大枚举
代码十分简单,这里不再摆出。
3.区间DP拓展知识之一:如何处理带环的区间DP?
针对此问,一般有两种做法:
-
1.既然是带环的区间
,说明一定有 条连边,而题中又只能连 次,说明有一条边是用不上的,于是我们可以跑 遍区间 ,枚举当每条边不被使用时的最优值。(时间复杂度 ) -
2.可以将这条链延长一倍,即将这条链复制下来接在其后面,最终结果为
中的最优值,时间复杂度 。
4.区间DP拓展知识之二:区间DP如何记录方案?
例题
设一个
每个节点都有一个分数(均为正整数),记第
任一棵子树
若某个子树为空,规定其加分为
叶子的加分就是叶节点本身的分数,不考虑它的空子树。
试求一棵符合中序遍历为
要求输出:
思路
采用
状态表示
-
1.集合:所有中序遍历是
这一段的二叉树集合 -
2.属性:
状态计算
我们先把
那么这棵树的价值就是其左,右子树的最大价值与根节点的价值之和,即:
然后最终答案就是
但是问题又来了:我们求出了最大结果,但是我们又要如何求出其对应加分二叉树的最大值呢?很简单,其实我们只需定义一个新数组
代码
#include <iostream>
#include <algorithm>
#include <cstring>
//define int long long
using namespace std;
#define N 35
#define For(i,j,k) for(int i=j;i<=k;i++)
#define IOS ios::sync_with_stdio(),cin.tie(),cout.tie()
int n, w[N], dp[N][N], g[N][N];
void dfs (int l, int r) {
if (l > r) return ; //如果输出完了,就退出
int root = g[l][r]; //g[l][r]代表当前子树的根
cout << root << ' '; //输出根
dfs (l, root - 1); //递归左子树
dfs (root + 1, r); //递归右子树
}
int main () {
IOS;
cin >> n;
For (i, 1, n) {
cin >> w[i];
}
For (len, 1, n) {
for (int l = 1; l + len - 1 <= n; l ++) {
int r = l + len - 1; //区间DP,存储左端点和右端点
if (len == 1) { //如果只有一个点,就依题意赋值
dp[l][r] = w[l];
g[l][r] = l;
} else {
For (k, l, r) { //枚举分界点
int le_sc = (k == l ? 1 : dp[l][k - 1]); //左边的分数
int ri_sc = (k == r ? 1 : dp[k + 1][r]); //右边的分数
int sum_sc = le_sc * ri_sc + w[k]; //总分
if (dp[l][r] < sum_sc) { //如果得到了更高的分数
dp[l][r] = sum_sc;
g[l][r] = k; //将这一段的根更新
}
}
}
}
}
cout << dp[1][n] << endl; //输出最大分值
dfs (1, n); //递归输出前序遍历
return 0;
}
4.区间DP拓展知识之三:如何处理二维区间DP?
例题
思路
看到这样一大坨公式,我们首先想到整理它:
才怪,我们要求均方差最小,所以当然是整理均方差啦!
首先去掉根号,不影响最小值:
然后展开完全平方式:
再把括号也展开:
然后我们发现
让它变得直观一点:
然后我们把分子合并同类项,得:
再用分子两项分别除以分母,得:
因为
分析
状态表示
-
1.集合:将子矩阵
切成 部分的所有方案 -
2.属性:
的最小值
状态计算
首先考虑这道题有多种情况讨论:对于每次切割,有横切和纵切两种切法。
在两种切法下各有
假如我们在
然后下面的因为已经不会再更改了,即
注意事项
-
1.因为 每一刀丢弃的部分的和是一个固定值,所以可以使用 二维前缀和 记录。
-
2.因为本题需要用到五维空间,写循环肯定会超时,所以需采用 记忆化搜索 解决。
代码
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;
#define N 15
#define M 9
#define For(i,j,k) for(int i=j;i<=k;i++)
#define IOS ios::sync_with_stdio(),cin.tie(),cout.tie()
const double INF = 1e9;
int n, m = 8, s[N][N];
double X/*bar{x}*/, dp[M][M][M][M][N];
//用大写X存储平均数
double get (int x1, int y1, int x2, int y2) { //套公式算丢掉部分的和
double sum = s[x2][y2] - s[x2][y1 - 1] - s[x1 - 1][y2] + s[x1 - 1][y1 - 1] - X;
return sum * sum / n;
}
double Dp (int x1, int y1, int x2, int y2, int k) {
double &v = dp[x1][y1][x2][y2][k];
if (v >= 0) return v; //如果这个矩阵已经被算过了,就直接返回
if (k == 1) return v = get (x1, y1, x2, y2); //如果不能再切了,就将这个矩阵 “丢掉”
v = INF;
For (i, x1, x2 - 1) { //横着切
v = min (v, Dp (x1, y1, i, y2, k - 1) + get (i + 1, y1, x2, y2)); //取上面
v = min (v, Dp (i + 1, y1, x2, y2, k - 1) + get (x1, y1, i, y2)); //取下面
}
For (i, y1, y2 - 1) { //竖着切
v = min (v, Dp (x1, y1, x2, i, k - 1) + get (x1, i + 1, x2, y2)); //取左边
v = min (v, Dp (x1, i + 1, x2, y2, k - 1) + get (x1, y1, x2, i)); //取右边
}
return v;
}
int main () {
IOS;
cin >> n;
For (i, 1, m) {
For (j, 1, m) {
cin >> s[i][j];
s[i][j] = s[i][j] + s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1];
//二维前缀和公式
}
}
memset (dp, -1, sizeof dp); //先将dp数组初始化
X = (double) s[m][m] / n; //将平均值赋值,一定要注意精度!!!!!!!!!!
printf ("%.3lf\n", sqrt (Dp (1, 1, 8, 8, n))); //输出整个矩阵切n刀的最大价值
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!