AcWing 320 能量项链
能量项链
一、题目描述
在 星球上,每个 人都随身佩带着一串能量项链,在项链上有 颗能量珠。
能量珠是一颗有头标记与尾标记的珠子,这些标记对应着某个正整数。
并且,对于相邻的两颗珠子,前一颗珠子的尾标记一定等于后一颗珠子的头标记。
因为只有这样,通过吸盘(吸盘是 人吸收能量的一种器官)的作用,这两颗珠子才能聚合成一颗珠子,同时释放出可以被吸盘吸收的能量。
如果前一颗能量珠的头标记为 ,尾标记为 ,后一颗能量珠的头标记为 ,尾标记为 ,则聚合后释放的能量为 ( 单位),新产生的珠子的头标记为 ,尾标记为 。
需要时, 人就用吸盘夹住相邻的两颗珠子,通过聚合得到能量,直到项链上只剩下一颗珠子为止。
显然,不同的聚合顺序得到的总能量是不同的,请你设计一个聚合顺序,使一串项链释放出的总能量最大。
例如:设 , 颗珠子的头标记与尾标记依次为 。
我们用记号 表示两颗珠子的聚合操作, 表示第 , 两颗珠子聚合后所释放的能量。则第 两颗珠子聚合后释放的能量为:。
这一串项链可以得到最优值的一个聚合顺序所释放的总能量为 。
输入格式
输入的第一行是一个正整数 ,表示项链上珠子的个数。
第二行是 个用空格隔开的正整数,所有的数均不超过 ,第 个数为第
颗珠子的头标记,当 时,第 颗珠子的尾标记应该等于第 颗珠子的头标记,第 颗珠子的尾标记应该等于第 颗珠子的头标记。
至于珠子的顺序,你可以这样确定:将项链放到桌面上,不要出现交叉,随意指定第一颗珠子,然后按顺时针方向确定其他珠子的顺序。
输出格式
输出只有一行,是一个正整数 ,为一个最优聚合顺序所释放的总能量。
数据范围
输入样例:
4
2 3 5 10
输出样例:
710
二、数据样例解析
第二行是个用空格隔开的正整数,所有的数均不超过 ,第 个数为第 颗珠子的头标记,当 时,第 颗珠子的尾标记应该等于第 颗珠子的头标记,第 颗珠子的尾标记应该等于第 颗珠子的头标记。
上面这段话,就是告诉我们,其实 是一个首尾相连接的环。
样例:
表示有个珠子:, ,,
如果我们先合并的和,最终结果就是,释放能量点,剩下
再与合并,结果就是,释放能量点。剩下和
与合并,结果就是,释放能量点。剩下,只剩下一个了,完成!
一共合并次,总的能量就是点
三、理解题意
本题是上一题环形石子合并问题的变形。按照上一题一样的处理环形问题的方法,将序列的长度翻倍。
状态表示
表示第到第个珠子合并释放的最大能量
这里 注意 比如为时,
合并这两颗珠子,释放的能量等于。
最后剩下一棵珠子的头尾吸盘也需要合并到一起的,比如 合并成为了,尽管此时合并成了一串,但项链的首尾仍需要连接,所以还需要合并及,本题就转化成了求区间长度为的石子合并问题的最大值。
四、区间
#include <bits/stdc++.h>
using namespace std;
const int N = 210;
const int INF = 0x3f3f3f3f;
int n;
int a[N];
int f[N][N];
int main() {
scanf("%d", &n);
// 破环成链
for (int i = 1; i <= n; i++) scanf("%d", &a[i]), a[i + n] = a[i]; // 第 i 颗珠子的头标记
// 区间DP模板
for (int len = 2; len <= n; len++)
for (int l = 1; l + len - 1 <= n * 2; l++) { // 区间左端点
int r = l + len - 1; // 区间右端点,r-l=l+len-1-l=len-1,举栗子:比如r=3,l=1,则len=3,len-1=3-1=2,符合事实
for (int k = l; k < r; k++) // 中间点k
f[l][r] = max(f[l][r], f[l][k] + f[k + 1][r] + a[l] * a[k + 1] * a[r + 1]);
// 这里的过程同石子合并,这里不难想到若将l到k的珠子合并之后会变成一个首是l而尾k+1的珠子;
// 同理若将k+1到r的珠子合并之后会变成一个首是k+1而尾r+1的珠子;
}
int res = 0;
for (int i = 1; i <= n; i++) res = max(f[i][i + n - 1], res); // 区间长度为n
printf("%d\n", res);
return 0;
}
五、记忆化搜索代码
#include <bits/stdc++.h>
using namespace std;
const int N = 210;
int n;
int a[N];
int f[N][N]; // 记忆化结果
// 计算l~r之间的结果值
int dfs(int l, int r) {
if (r == l) return 0; // 区间内有一个数字
if (r == l + 1) return (a[l] * a[r] * a[r + 1]); // 区间内有两个数字,a[r+1]就是破环成链的妙用
int &v = f[l][r]; // 准备记录结果
if (v) return v; // 如果计算过,则返回已经有的结果
// 枚举倒数第一个可能的结束位置
for (int k = l; k < r; k++)
v = max(v, dfs(l, k) + dfs(k + 1, r) + a[l] * a[k + 1] * a[r + 1]);
return v;
}
int main() {
scanf("%d", &n);
// 破环成链
for (int i = 1; i <= n; i++) scanf("%d", &a[i]), a[i + n] = a[i];
// 以每个位置为起点,跑一遍dfs,找出最大值
int res = 0;
for (int i = 1; i <= n; i++) res = max(res, dfs(i, i + n - 1));
printf("%d", res);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
2016-01-05 试题识别与生成