@description@
给定 N 张排成一行的卡片,第 i 张卡片上面写着 Ai。
重复以下操作,直到只剩下两张卡片。
取出卡片 i,将卡片 i 左边的卡片与卡片 i 右边的卡片的 A 加上 Ai。
求最后剩下的两张卡片的 A 的可能的最小和。
Constraints
2≤N≤18, 0≤Ai≤10^9(1≤i≤N)
Input
输入格式如下:
N
A1 A2 ... AN
Output
输出最小和。
Sample Input 1
4
3 1 4 2
Sample Output 1
16
先选 1 得到 4 5 2,再选此时的 5 得到 9 7,最小和 9 + 7 = 16。
@solution@
考虑最朴素的暴力:枚举排列,表示卡片被取走的顺序,然后算贡献。
显然不够优秀。
注意到对于按序排放的卡片 a b c,假如 b 不被取走,则 a, c 之间取的顺序并不重要。
这意味着我们重复枚举了很多结果一样的状态。
一个 Ai 贡献次数 = 左边第一个比它后取走的卡片贡献次数 + 右边第一个比它后取走的卡片贡献次数。我们总是认为第 1 张卡片与第 N 张卡片是最后取走的,且贡献次数为 1。
考虑一种基于笛卡尔树的贡献计算方法:
从根开始向下递归,同时维护该子树 左边第一个比它后取走的卡片贡献次数 与 右边第一个比它后取走的卡片贡献次数。那么就可以算出每个结点的贡献次数。
那么我在搜索的时候可以一边枚举笛卡尔树的形态一边计算贡献。
这样总搜索量 = 16 个点组成的二叉树数量 = 第 16 个卡特兰数 = 35357670。可以通过该题目。
当然你可以用记忆化搜索。不过没有必要,而且 map 常数大,hash 反而麻烦了。
@accepted code@
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
const int MAXN = 20;
const ll INF = (1LL<<60);
int N; ll A[MAXN + 5];
ll dfs(int l, int r, int cntl, int cntr) {
if( l + 1 == r ) return 0;
ll ret = dfs(l, l+1, cntl, cntl+cntr) + dfs(l+1, r, cntl+cntr, cntr) + (cntl+cntr)*A[l+1];
for(int i=l+2;i<=r-1;i++)
ret = min(ret, dfs(l, i, cntl, cntl+cntr) + dfs(i, r, cntl+cntr, cntr) + (cntl+cntr)*A[i]);
return ret;
}
int main() {
scanf("%d", &N);
for(int i=1;i<=N;i++)
scanf("%lld", &A[i]);
printf("%lld
", dfs(1, N, 1, 1) + A[1] + A[N]);
}
@details@
我连暴搜都不会了.jpg。
感觉用笛卡尔树理解起来更为直观,而且也更容易证明复杂度。