【题目描述】
在Mars星球上,每个Mars人都随身佩带着一串能量项链。在项链上有N颗能量珠。能量珠是一颗有头标记与尾标记的珠子,这些标记对应着某个正整数。并且,对于相邻的两颗珠子,前一颗珠子的尾标记一定等于后一颗珠子的头标记。因为只有这样,通过吸盘(吸盘是Mars人吸收能量的一种器官)的作用,这两颗珠子才能聚合成一颗珠子,同时释放出可以被吸盘吸收的能量。如果前一颗能量珠的头标记为$m$,尾标记为$r$,后一颗能量珠的头标记为$r$,尾标记为$n$,则聚合后释放的能量为$m * r* n$(Mars单位),新产生的珠子的头标记为$m$,尾标记为$n$。
需要时,Mars人就用吸盘夹住相邻的两颗珠子,通过聚合得到能量,直到项链上只剩下一颗珠子为止。显然,不同的聚合顺序得到的总能量是不同的,请你设计一个聚合顺序,使一串项链释放出的总能量最大。
【题目链接】
CodeVS 1154 能量项链 【NOIP 2006】
【解题思路】
这是一个环,首先想到将其长度翻倍,展开成链,转化成区间上问题。
这样环以$i$为起点就对应了链上的区间$[i, i + n)$。
然后进行区间DP,设$f[l][r]$表示区间$[l, r]$上的珠子聚合后释放的最大能量。 用 $a_i$ 表示第 $i$ 颗珠子的头标记(也是第 $i + 1$ 颗珠子的尾标记),转移如下。 $$ f[l][r] = max\{f[l][k] + f[k + 1][r] + a_l * a_{k + 1} * a_{r + 1}\},k \in [l, r) $$
即枚举区间$[l,r]$是由区间$[l, k]$和$[k + 1, r]$合并而来,取最大值。
时间复杂度$O(n^3)$。
【AC代码】
采用记忆化搜索实现。
#include <cstdio>
#include <climits>
#include <algorithm>
#include <cstring>
#define MAXN 100
int a[(MAXN << 1) + 1];
int f[MAXN << 1][MAXN << 1];
bool visited[MAXN << 1][MAXN << 1];
int search(int l, int r){
// [l, r]
int &ans = f[l][r];
if(visited[l][r]) return ans;
else visited[l][r] = true;
if(l == r) ans = 0;
else if(r - l == 1) ans = a[l] * a[r] * a[r + 1];
else{
ans = INT_MIN;
// [l, k] + [k + 1][r]
for(int k = l; k < r; k++){
ans = std::max(ans, search(l, k) + search(k + 1, r) + a[l] * a[k + 1] * a[r + 1]);
}
}
return ans;
}
int main(){
int n;
scanf("%d", &n);
for(int i = 0; i < n; i++) scanf("%d", a + i), a[i + n] = a[i];
a[n << 1] = a[0]; // 处理访问最后一个珠子的尾标记时的越界情况
search(0 , 2 * n - 1);
int ans = INT_MIN;
for(int i = 0; i < n; i++) ans = std::max(ans, f[i][i + n - 1]);
printf("%d\n", ans);
return 0;
}
就是这样啦。