【AtCoder Grand 035D】Add and Remove 题解

题目大意

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

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

题解

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

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

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

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

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

  分析一波复杂度。首先是状态数,l,rl,r 总共是 O(n2)O(n^2),至于 x,yx,y,它与 dp 时选择哪个 ii 无关,每往下走一层会产生两种新的状态,因此总量是 O(2n)O(2^n) 的。因此状态数是 O(2nn2)O(2^nn^2) 的,加上转移后时间就是 O(2nn3)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]);
}