@atcoder - AGC035D@ Add and Remove


@description@

给定 N 张排成一行的卡片,第 i 张卡片上面写着 Ai。code

重复如下操做,直到只剩下两张卡片。
取出卡片 i,将卡片 i 左边的卡片与卡片 i 右边的卡片的 A 加上 Ai。递归

求最后剩下的两张卡片的 A 的可能的最小和。ip

Constraints
2≤N≤18, 0≤Ai≤10^9(1≤i≤N)hash

Input
输入格式以下:
N
A1 A2 ... ANit

Output
输出最小和。io

Sample Input 1
4
3 1 4 2
Sample Output 1
16class

先选 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\n", dfs(1, N, 1, 1) + A[1] + A[N]);
}

@details@

我连暴搜都不会了.jpg。

感受用笛卡尔树理解起来更为直观,并且也更容易证实复杂度。

相关文章
相关标签/搜索