环形石子合并能量项链加分二叉树凸多边形的划分棋盘分割
环形石子合并将 n 堆石子绕圆形操场排放,现要将石子有序地合并成一堆。
规定每次只能选相邻的两堆合并成新的一堆,并将新的一堆的石子数记做该次合并的得分。
请编写一个程序,读入堆数 n 及每堆的石子数,并进行如下计算:
选择一种合并石子的方案,使得做 n−1 次合并得分总和最大。选择一种合并石子的方案,使得做 n−1 次合并得分总和最小。
输入格式
第一行包含整数 n,表示共有 n 堆石子。
第二行包含 n 个整数,分别表示每堆石子的数量。
输出格式
输出共两行:
第一行为合并得分总和最小值,
第二行为合并得分总和最大值。
数据范围
1 ≤ n ≤ 200
输入样例:
4 4 5 9 4
输出样例:
43 54
分析
拓展:
如果每轮合并的石子 可以是任意 的 两堆 石子,那么用到的就是经典的 Huffman Tree 的二叉堆模型
如果每轮合并的石子 可以是任意 的 nn 堆 石子,那么用到的就是经典的 Huffman Tree 的 nn 叉堆模型
以上两种题型可以参考:二叉堆:AcWing 148. 合并果子nn 叉堆:AcWing 149. 荷马史诗
回归本题,本题要求每轮合并的石子 必须是相邻的 两堆石子,因此不能采用 Huffman Tree 的模型
这类限制只能合并相邻两堆石子的模型,用到的是经典的 区间DP 模型
考虑如何设定 动态规划 的阶段,既可以表示出初始每个石子的费用,也可以表示出合并后整个堆的费用
不妨把当前合并的石子堆的大小作为DP的阶段
这样 len=1 表示初值,即每个堆只有一个石子; len=n 表示终值,即一个堆中有所有的石子
这种阶段设置方法保证了我们每次合并两个区间时,他们的所有子区间的合并费用都已经被计算出来了
阶段设定好后,考虑如何记录当前的状态,无外乎就两个参数:
- 石子堆的左端点 l石子堆的右端点 r
在区间DP中,我们也常常省去 lenlen 这一维的空间
因为 r−l+1=lenr−l+1=len,也就保证了在已知 ll 和 rr 的情况下,不会出现状态定义重复的情况
根据线性代数中方程的解的基本概念,我们就可以删掉 lenlen 这一维不存在的约束
但为了方便读者理解,以及介绍区间DP的阶段是如何划分的,我还是写了出来
以上就是所有有关石子合并的区间DP分析
在考虑一下本题的 环形相邻 情况如何解决,方案有如下两种:
- 我们可以枚举环中分开的位置,将环还原成链,这样就需要枚举 n 次,时间复杂度为 O(n4)我们可以把链延长两倍,变成 2n 个堆,其中 i 和 i+n 是相同的两个堆,然后直接套 区间DP 模板,但对于 阶段 len 只枚举到 n,根据 状态的定义,最终可以得到所求的方案,时间复杂度为 O(n3)
一般常用的都是第二种方法,我也只会演示第二种方法的写法,对第一种有兴趣的读者可以自行尝试
时间复杂度:O(n3)
#include#include using namespace std; const int N = 410; int n; int a[N], s[N]; int mx[N][N]; // 从i到j合并的最大值 int mi[N][N]; // 从i到j合并的最小值 int main() { cin >> n; for (int i = 1; i <= n; i ++ ) { cin >> a[i]; a[i + n] = a[i]; } // 前缀和 for (int i = 1; i <= 2 * n; i ++ ) s[i] = s[i - 1] + a[i]; // 初始化数组 memset(mx, -0x3f, sizeof mx); memset(mi, 0x3f, sizeof mi); // DP 迭代式 for (int len = 1; len <= n; len ++ ) // 区间长度 for (int l = 1; l + len - 1 <= 2 * n; l ++ ) { // 左右端点 int r = l + len - 1; if (l == r) mx[l][r] = mi[l][r] = 0; // 一堆不需要合并 else { // 枚举中间点 for (int k = l; k < r; k ++ ) { mx[l][r] = max(mx[l][r], mx[l][k] + mx[k + 1][r] + s[r] - s[l - 1]); mi[l][r] = min(mi[l][r], mi[l][k] + mi[k + 1][r] + s[r] - s[l - 1]); } } } // 求结果 int minv = 0x3f3f3f3f, maxv = -0x3f3f3f3f; for (int i = 1; i <= n; i ++ ) { minv = min(minv, mi[i][i + n - 1]); maxv = max(maxv, mx[i][i + n - 1]); } cout << minv << endl << maxv; return 0; }
为什么枚举中间点(分开点)时,可以取到左端点,但是不可以取到右端点?
经大佬点拨,明白,k 的取值范围,取决于定义的递推公式。以上代码定义的 k 为左半区间的右端点作为中间点,故 k 的取值范围为 l <= k < r,这样左半、右半区间都可以有仅取一个点的情况,不重不漏实现对左右区间的分割。
也可以将 k 定义为右半区间的左端点,这样k的范围就不一样了。
蒟蒻再次感谢彩色铅笔大佬
能量项链笔记学习:
作者:彩色铅笔
链接:https://www.acwing.com/solution/content/59932/
来源:AcWing
在 Mars 星球上,每个 Mars 人都随身佩带着一串能量项链,在项链上有 N 颗能量珠。
能量珠是一颗有头标记与尾标记的珠子,这些标记对应着某个正整数。
并且,对于相邻的两颗珠子,前一颗珠子的尾标记一定等于后一颗珠子的头标记。
因为只有这样,通过吸盘(吸盘是 Mars 人吸收能量的一种器官)的作用,这两颗珠子才能聚合成一颗珠子,同时释放出可以被吸盘吸收的能量。
如果前一颗能量珠的头标记为 m,尾标记为 r,后一颗能量珠的头标记为 r,尾标记为 n,则聚合后释放的能量为 m × r × n(Mars 单位),新产生的珠子的头标记为 m,尾标记为 n。
需要时,Mars 人就用吸盘夹住相邻的两颗珠子,通过聚合得到能量,直到项链上只剩下一颗珠子为止。
显然,不同的聚合顺序得到的总能量是不同的,请你设计一个聚合顺序,使一串项链释放出的总能量最大。
例如:设 N=4,4 颗珠子的头标记与尾标记依次为 (2,3)(3,5)(5,10)(10,2)。
我们用记号 ⊕ 表示两颗珠子的聚合操作,(j⊕k) 表示第 j,k 两颗珠子聚合后所释放的能量。则
第 4、1 两颗珠子聚合后释放的能量为:(4⊕1)=10×2×3=60。
这一串项链可以得到最优值的一个聚合顺序所释放的总能量为 ((4⊕1)⊕2)⊕3)=10×2×3+10×3×5+10×5×10=710。
输入格式
输入的第一行是一个正整数 N,表示项链上珠子的个数。
第二行是 N 个用空格隔开的正整数,所有的数均不超过 1000,第 i 个数为第 i 颗珠子的头标记,当 i 至于珠子的顺序,你可以这样确定:将项链放到桌面上,不要出现交叉,随意指定第一颗珠子,然后按顺时针方向确定其他珠子的顺序。 输出格式 数据范围 输入样例: 输出样例: 设一个 n 个节点的二叉树 tree 的中序遍历为(1,2,3,…,n),其中数字 1,2,3,…,n 为节点编号。 每个节点都有一个分数(均为正整数),记第 i 个节点的分数为 di,tree 及它的每个子树都有一个加分,任一棵子树 subtree(也包含 tree 本身)的加分计算方法如下: subtree的左子树的加分 × subtree的右子树的加分 + subtree的根的分数 若某个子树为空,规定其加分为 1。 叶子的加分就是叶节点本身的分数,不考虑它的空子树。 试求一棵符合中序遍历为(1,2,3,…,n)且加分最高的二叉树 tree。 要求输出: (1)tree的最高加分 (2)tree的前序遍历 输入格式 第 2 行:n 个用空格隔开的整数,为每个节点的分数(0<分数<100)。 输出格式 第 2 行:n 个用空格隔开的整数,为该树的前序遍历。如果存在多种方案,则输出字典序最小的方案。 数据范围 输入样例: 输出样例: 算法 (区间DP,二叉树的遍历) O(n3) 状态表示: f[i][j] 表示中序遍历是 w[i ~ j] 的所有二叉树的得分的最大值。 状态计算: f[i][j] = max(f[i][k - 1] * f[k + 1][j] + w[k]),即将f[i][j]表示的二叉树集合按根节点分类,则根节点在 k 时的最大得分即为 f[i][k - 1] * f[k + 1][j] + w[k],则f[i][j]即为遍历 k 所取到的最大值。 在计算每个状态的过程中,记录每个区间的最大值所对应的根节点编号。 那么最后就可以通过DFS求出最大加分二叉树的前序遍历了。 时间复杂度 笔记、代码学习:
输出只有一行,是一个正整数 E,为一个最优聚合顺序所释放的总能量。
4 ≤ N ≤ 100,
1 ≤ E ≤ 2.1×1094
2 3 5 10
710
#include
加分二叉树
第 1 行:一个整数 n,为节点个数。
第 1 行:一个整数,为最高加分(结果不会超过int范围)。
n < 305
5 7 1 2 10
145
3 1 2 4 5
状态总数是 O(n2),计算每个状态需要 O(n) 的计算量,因此总时间复杂度是 O(n3)。#include
作者:yxc
链接:https://www.acwing.com/solution/content/3804/
来源:AcWing
凸多边形的划分
棋盘分割



