一、数据样例解析
第二行是\(N\)个用空格隔开的正整数,所有的数均不超过 \(1000\),第 \(i\) 个数为第 \(i\) 颗珠子的头标记,当 \(i<N\) 时,第 \(i\) 颗珠子的尾标记应该等于第 \(i+1\) 颗珠子的头标记,第 \(N\) 颗珠子的尾标记应该等于第 \(1\) 颗珠子的头标记。
上面这段话,就是告诉我们,其实,是一个首尾相连接的环。
样例: \(2\) \(3\) \(5\) \(10\)
表示有\(4\)个珠子:\(①[2,3]\), \(②[3,5]\),\(③[5,10]\),\(④[10,2]\)
如果我们先合并的\(④\)和\(①\),最终结果就是\(⑤[10,3]\),释放能量\(10*2*3=60\)点,剩下\(②③⑤\)
\(⑤\)再与\(②\)合并,结果就是\(⑥[10,5]\),释放能量\(10*3*5=150\)点。剩下\(③\)和\(⑥\)
\(⑥\)与\(③\)合并,结果就是\(⑦[10,10]\),释放能量\(10*5*10=500\)点。剩下\(⑦\),只剩下一个了,完成!
一共合并\(3\)次,总的能量就是\(60+150+500=710\)点
二、理解题意
本题是上一题环形石子合并问题的变形,或者说更类似于矩阵连乘问题。按照上一题一样的处理环形问题的方法,将序列的长度翻倍。
状态表示
\(f[l][r]\)表示第\(l\)到第\(r\)个数合并释放的最大能量
这里注意比如\(len\)为\(3\)时,\(2\) \(3\) \(5\)实际上只是两颗珠子包含的信息,即\((2,3)\)和\((3,5)\)因此合并\(2\) \(3\) \(5\)释放的能量等于\(2 * 3 *5\)。
最后剩下一棵珠子的头尾吸盘也需要合并到一起的,比如\(2\) \(3\) \(5\) \(10\)合并成为了\((2,10)\),尽管此时合并成了一串,但项链的首尾仍需要连接,所以还需要合并\((2,10)\)及\((10,2)\),本题就转化成了求区间长度为\(n + 1\)的石子合并问题的最大值。
以往的 区间DP 我们是把区间 \([a,b]\) 拆分为 \([a,k]\)和\([k+1,b]\)
因为 同一个石子 只会被合并到 一个石子堆 里
但本题合并魔法石时,分割点 \(k\) 要被分到 左侧石子堆的右端点 和 右侧石子堆的左端点 中
因此,参数 \(k\) 要作为两个区间的共同端点来使用,即 \([a,k]\) 和 \([k,b]\)
此外我们原来只需要合并 \(n\) 个石头,这样转换后就要合并 \(n+1\) 个石头了
三、dfs实现代码
#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; //如果计算过,则返回已经有的结果
//枚举倒数第一个可能的结束位置
//区间 l..........(i-1).i..................r
//分成 |<------------->|<------------------>|
//新区间为 l..(i-1) i..r
//即 (l,l+1)..(i-1,i) 和 (i,i+1)..(r,r+1) 注意最后是r+1 !!
for (int i = l + 1; i <= r; i++)
v = max(v, dfs(l, i - 1) + dfs(i, r) + a[l] * a[i] * a[r + 1]);
return v;
}
int main() {
cin >> n;
//破环成链
for (int i = 1; i <= n; i++)cin >> a[i], a[i + n] = a[i];
int res = 0;
//以每个位置为起点,跑一遍dfs,找出最大值
for (int i = 1; i <= n; i++) res = max(res, dfs(i, i + n - 1));
printf("%d", res);
return 0;
}
四、DP实现代码I
#include <bits/stdc++.h>
//把合并n颗珠子的问题转化为合并(n+1)个数合并的问题,只不过有一个数是公用的,注意不要间断分割
//区间长度为n+1
using namespace std;
const int N = 210;//开两倍的空间
int n;
int w[N];
int f[N][N]; //左,右端点间的能量最大值
int main() {
cin >> n;
//环形DP的标准路线,双倍长度,w[i+n]=w[i]
for (int i = 1; i <= n; i++) {
cin >> w[i];
w[i + n] = w[i];
}
//因为最后需要扣头,所以区间长度是n+1
//长度是3才能构成两个珠子,才能对接释放能量
for (int len = 3; len <= n + 1; len++) //枚举有效区间长度
for (int l = 1; l + len - 1 <= n * 2; l++) { //枚举左端点
int r = l + len - 1; //计算右端点
for (int k = l + 1; k < r; k++) //枚举分界点,模拟最后一次是在哪个位置合并的
f[l][r] = max(f[l][r], f[l][k] + f[k][r] + w[l] * w[r] * w[k]);
}
//枚举所有区间长度是n+1的区间,取一个最大值
int res = 0;
for (int i = 1; i <= n; i++)//枚举每个左端点,长度为n+1的区间,能量最大值是多少
res = max(res, f[i][i + n]);
//输出
printf("%d", res);
return 0;
}
五、DP实现代码II
#include <bits/stdc++.h>
using namespace std;
//直接当成珠子来看待即可,模拟合并过程。
//区间长度为n
const int N = 210;
const int INF = 0x3f3f3f3f;
int n;
int a[N];
int f[N][N];
int main() {
cin >> n;
//破环成链
for (int i = 1; i <= n; i++) cin >> a[i], a[i + n] = a[i];
for (int len = 2; len <= n; len++)
for (int l = 1; l + len - 1 <= n * 2; l++) {
int r = l + len - 1;
for (int k = l; k < r; 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 = -INF;
for (int i = 1; i <= n; i++) res = max(f[i][i + n - 1], res);//区间长度为n
cout << res << endl;
return 0;
}