【AtCoder Grand 035D】Add and Remove 题解

题目大意

  有 $n$ 张牌,写有数字 $a_1,\cdots,a_n$。
  每一轮操作,选择连续的三张牌,吃掉中间那张,然后把中间那张的数字加到其余两张上。
  直到只剩两张牌为止。
  目标是使得最后剩下的两张牌的数字和最小,输出最小的和。

  $2 \leq n \leq 18,~0 \leq a_i \leq 10^9$
  时限 2s

题解

  最近 atcoder 的模型怎么都这么。。。

  要考虑每张牌的贡献,实际上就是想要知道每张牌被算了多少次。

  然后发现算这个贼麻烦,它对左右都有贡献,还跟顺序有关。。。

  所以题解又给了个好办法。

  一开始,最左和最右这两张牌都是贡献 $1$ 次的。
  对于当前区间 $[l,r]$,枚举最后被吃掉的是哪张牌,假设是 $i$,那么 $i$ 这张牌的贡献次数就是 $l$ 的次数加上 $r$ 的次数。
  设 $f_{l,r,x,y}$ 表示当前 $[l,r]$ 这个区间,$l$ 被贡献了 $x$ 次,$r$ 被贡献了 $y$ 次,所能达到的最小代价和。于是就枚举 $i$ 然后 dp 下去了。

  分析一波复杂度。首先是状态数,$l,r$ 总共是 $O(n^2)$,至于 $x,y$,它与 dp 时选择哪个 $i$ 无关,每往下走一层会产生两种新的状态,因此总量是 $O(2^n)$ 的。因此状态数是 $O(2^nn^2)$ 的,加上转移后时间就是 $O(2^nn^3)$ 跑不满。

  用记忆化搜索实现,代码贼短。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include<bits/stdc++.h>
#define fo(i,a,b) for(int i=a;i<=b;i++)
using namespace std;

typedef long long LL;
typedef pair<pair<int,int>,pair<LL,LL>> pr;

const int maxn=20;

int n;
LL a[maxn];

map<pr,LL> f;
LL dfs(int l,int r,LL x,LL y)
{
if (l+1>=r) return 0;
pr now=make_pair(make_pair(l,r),make_pair(x,y));
if (f.count(now)) return f[now];
LL re=5e18;
fo(i,l+1,r-1) re=min(re,dfs(l,i,x,x+y)+dfs(i,r,x+y,y)+a[i]*(x+y));
return f[now]=re;
}

int main()
{
scanf("%d",&n);
fo(i,1,n) scanf("%lld",&a[i]);

printf("%lld\n",dfs(1,n,1,1)+a[1]+a[n]);
}