1 条题解
-
0
题目大意
给定 个元素,每个元素有 两个属性,初始第 个元素为 ,合并元素 的代价为 ,合并后形成新元素 ,求把所有元素合并成一个的最小代价。
数据范围:。
思路分析
考虑把元素的合并看成一棵二叉树,记每个点 子树的最大深度为 ,那么所有 的贡献就是所有非根节点的 。
而 的贡献可以看成二叉树上每个点选一个子树系数 ,每个叶子的系数就表示该叶子对应 对答案的贡献系数。
容易发现每个叶子的具体排列不重要,确定二叉树结构后把权值最大的放到系数最小的位置上即可。
因此我们只关心 个系数构成的每一种可重集 。
可以暴力 dp, 表示子树内最大深度为 ,构成系数可重集为 时, 贡献的最小值。
转移时枚举两个状态合并,但这样复杂度太高,无法通过。
我们考虑自上而下地维护这棵二叉树:即从根节点开始,每次把树上的一个叶子分裂出两个儿子节点。
但是每次分裂的时候会影响树上原有节点的 ,这就需要记录一些和树形态有关的信息,这是完全不能接受的。
考虑优化,注意到一个子树最大深度为 的点,我们可以钦定他在倒数第 次操作时才进行第一次分裂,那么此后的每一次分裂他的最大深度都 ,很显然这个过程不改变最优解。
因此存在一种分裂的方式,使得每次分裂后每个非叶节点的最大深度都 ,那么 就会翻倍,也容易求出分裂后的 。
但是这么做还不足以通过,首先发现答案不超过 ,可以用来优化 的上界。
其次我们发现按照上述钦定的过程分裂,前一次分裂过的节点的两个儿子中至少有一个这次操作也会分裂,因此每次分裂的节点数单调不降,记录上一次分裂的节点个数即可。
时间复杂度 ,其中 表示叶子数 时的总状态数, 表示叶子数 时的总状态数, 时 。
代码呈现
#include<bits/stdc++.h> #define ll long long using namespace std; const ll inf=1.7e11; typedef vector<int> vi; map <vi,array<ll,2>> f[105]; //[tree size][leaf coef] {min val,low} ll a[105]; void solve() { int n; scanf("%d",&n); for(int i=0;i<n;++i) scanf("%lld",&a[i]); sort(a,a+n,greater<ll>()); ll ans=inf; for(auto &it:f[n]) { const vi&c=it.first; ll s=it.second[0]; for(int i=0;i<n;++i) s+=c[i]*a[i]; ans=min(ans,s); } printf("%lld\n",ans); } signed main() { const int n=100; f[1][{0}]={0,1}; for(int i=1;i<=n;++i) for(auto &it:f[i]) { const vi&c=it.first; vi d=it.first; ll w=it.second[0],lim=it.second[1]; for(int j=1;j<=i&&i+j<=n;++j) { d.insert(upper_bound(d.begin(),d.end(),c[j-1]+1),c[j-1]+1); if(j>=lim) { ll nw=2*w+(i+j-2); if(nw>inf) break; if(!f[i+j].count(d)) f[i+j][d]={nw,j}; else { auto &z=f[i+j][d]; z=min(z,array<ll,2>{nw,j}); } } } } int T; scanf("%d",&T); while(T--) solve(); return 0; }
- 1
信息
- ID
- 7294
- 时间
- 1500ms
- 内存
- 512MiB
- 难度
- 10
- 标签
- 递交数
- 2
- 已通过
- 1
- 上传者