AGC035D Add and Remove
题意
给出\(n\)个数,每次删除一个不在两端的数,然后把它的权值加到相邻的两个数上。
问操作\(n-2\)次后,所剩的两数之和的最小值
\(n\le18\)
题解
暴力存储每一个数的状态肯定不行。
考虑计算每一个数被计算了多少次。
可以发现\(1\)和\(n\)一定只被计算了1次
最后一个被消除掉的数应只被计算\(2\)次
可以发现,如果左端点被计算\(x\)次,右端点被计算\(y\)次
那么左右端点之间最后一个被消除的数被计算了\(x+y\)次
然后就可以开始记忆化搜索了
注意全部都用\(map\)存储状态会\(TLE\),需要把其中一部分用数组存储
代码
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int sz=20; int n; struct node{ int l,r,fl,fr; const bool operator<(const node &p)const{ if(l!=p.l) return l<p.l; if(r!=p.r) return r<p.r; if(fl!=p.fl) return fl<p.fl; if(fr!=p.fr) return fr<p.fr; } }; ll a[sz]; ll g[sz][sz][611][611]; map<node,ll>dp; ll f(int l,int r,int fl,int fr){ if(l+1==r) return 0; if(fl<611&&fr<611){ if(g[l][r][fl][fr]) return g[l][r][fl][fr]; } else if(dp[(node){l,r,fl,fr}]) return dp[(node){l,r,fl,fr}]; ll ret=1e18; ll sum=fl+fr; for(int i=l+1;i<=r-1;i++){ ret=min(ret,sum*a[i]+f(l,i,fl,sum)+f(i,r,sum,fr)); } if(fl<611&&fr<611) return g[l][r][fl][fr]=ret; else return dp[(node){l,r,fl,fr}]=ret; } int main(){ scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%lld",&a[i]); printf("%lld\n",a[1]+f(1,n,1,1)+a[n]); }