【题目描述】

在Mars星球上,每个Mars人都随身佩带着一串能量项链。在项链上有N颗能量珠。能量珠是一颗有头标记与尾标记的珠子,这些标记对应着某个正整数。并且,对于相邻的两颗珠子,前一颗珠子的尾标记一定等于后一颗珠子的头标记。因为只有这样,通过吸盘(吸盘是Mars人吸收能量的一种器官)的作用,这两颗珠子才能聚合成一颗珠子,同时释放出可以被吸盘吸收的能量。如果前一颗能量珠的头标记为m,尾标记为r,后一颗能量珠的头标记为r,尾标记为n,则聚合后释放的能量为mrn(Mars单位),新产生的珠子的头标记为m,尾标记为n

需要时,Mars人就用吸盘夹住相邻的两颗珠子,通过聚合得到能量,直到项链上只剩下一颗珠子为止。显然,不同的聚合顺序得到的总能量是不同的,请你设计一个聚合顺序,使一串项链释放出的总能量最大。

【题目链接】

CodeVS 1154 能量项链 【NOIP 2006】

【解题思路】

这是一个环,首先想到将其长度翻倍,展开成链,转化成区间上问题。

这样环以i为起点就对应了链上的区间[i,i+n)

然后进行区间DP,设f[l][r]表示区间[l,r]上的珠子聚合后释放的最大能量。 用 ai 表示第 i 颗珠子的头标记(也是第 i+1 颗珠子的尾标记),转移如下。 f[l][r]=max{f[l][k]+f[k+1][r]+alak+1ar+1},k[l,r)

即枚举区间[l,r]是由区间[l,k][k+1r]合并而来,取最大值。

时间复杂度O(n3)

【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;
}

就是这样啦。