T1AT5141 [AGC035D] Add and Remove
有一个由 N 张牌组成的牌堆,每一张牌上都写有一个非负整数。自顶部开始,第 i 张牌上的数字为 A_i。
Snuke 将重复以下操作,直至牌堆里只剩两张卡为止:
- 从牌堆里选择三张连续的卡。
- 把三张卡中位于中间位置的卡吃掉。
- 把剩余的两张卡上的数字加上被吃掉的卡的数字后按照原来的顺序放回牌堆。
请找出最后剩下的两张牌上所写的数字之和最小是多少。
n <= 18
贪心,考虑在两张牌中间插入一张,那插入的这张就会被统计 lt + rt次,lt是左边的牌的统计次数,rt同理。
于是乎可以设计出一个函数 dfs(l , r , xl , xr)
表示在l+1~r-1这段区间,左端点l被统计了xl次,右端点r被统计了xr次,的最小值。
(dfs(l , r , xl , xr) = min(dfs(l , p , xl , xl + xr) , dfs(p , r , xl + xr , xr)))
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<string>
#include<cstdio>
#include<vector>
#include<queue>
#include<cmath>
#include<set>
#include<map>
using namespace std;
typedef long long LL;
#define int long long
inline int read()
{
register int x = 0 , f = 0; register char c = getchar();
while(c < '0' || c > '9') f |= c == '-' , c = getchar();
while(c >= '0' && c <= '9') x = (x << 3) + (x << 1) + c - '0' , c = getchar();
return f ? -x : x;
}
int n;
int a[20];
int dfs(int l , int r , int xl , int xr)
{
if(r - l <= 1) return 0; // 开区间
int ans = 1e18;
for(int i = l + 1 ; i <= r - 1 ; ++i) // i 是最后一个选的,然后加上将左右两边合并的贡献
ans = min(ans , dfs(l , i , xl , xl + xr) + dfs(i , r , xl + xr , xr) + a[i] * (xl + xr));
return ans; // 开区间的好处, (l , pos) + (pos , r) + [pos , pos] = (l , r)
}
signed main()
{
n = read();
for(int i = 1 ; i <= n ; ++i) a[i] = read();
cout << dfs(1 , n , 1 , 1) + a[1] + a[n] << '
';
return 0;
}