[DP记录]AT4120 [ARC096D] Sweet Alchemy

command_block

2021-02-13 14:28:49

Personal

**题意** : 给出一棵有根树,每个节点上有无穷多个物品,购买节点 $u$ 的一个物品需要花费 $m_u$ 元。 记节点 $u$ 的物品购买了 $c_u$ 个。则需要满足 $c_{fa(u)}\leq c_u\leq c_{fa(u)}+d$ ,其中 $d$ 为给定常数。 问在有 $x$ 元的情况下最多购得多少个物品。 $n\leq 50,\ x,m_u,d\leq 10^9$ ,时限 $\texttt{2s}$。 ------------ 将 $c$ 数组做树上差分,记 $d_u=c_u-c_{fa(u)}$ ,则 $0\leq d_u\leq d$。 而将 $d_u$ 增加 $1$ 所需的代价是子树 $u$ 内 $m$ 的和。而受益则为子树 $u$ 的大小。 现在问题变成了 $n$ 个物品的多重背包。(注意根不受 $d$ 的限制) $x$很大,直接 $\rm DP$ 不可行。 不难想到一种贪心,即按照性价比选择,可惜的是,这个贪心是错误的。 这个贪心能排除一些较劣的解,简化决策 : 若有两个物品 $i,j$ 满足 $\dfrac{m_i}{v_i}<\dfrac{m_j}{v_j}$ ,且 $c_i\geq v_j$ ,显然可以将 $v_j$ 个 $i$ 换成 $v_i$ 个 $j$ ,这样收益没有变化,但是代价减少了。 这告诉我们,购买某个物品个数超过 $n$ 的部分可以贪心调整。所以每个物品保留 $O(n)$ 个做背包,区其余按照性价比贪心即可。 设 $f[k]$ 为获得收益为 $k$ 时最小的成本,类似多重背包转移即可。使用二进制拆分实现,复杂度为 $O(n^4\log n)$。 ```cpp #include<algorithm> #include<cstdio> #define ll long long using namespace std; const ll INF=1ll<<60; int lim; void add(ll *f,ll m,int v){ for (int i=lim;i>=v;i--) f[i]=min(f[i],f[i-v]+m); } struct Data{ll m;int v,c;}b[55]; bool cmp(const Data &A,const Data &B) {return 1ll*A.v*B.m>1ll*B.v*A.m;} int n,x,d,fa[55]; ll f[125500]; int main() { scanf("%d%d%d",&n,&x,&d); lim=n*n*n; scanf("%d",&b[1].m); for (int i=2;i<=n;i++) scanf("%lld%d",&b[i].m,&fa[i]); for (int i=n;i;i--){ b[fa[i]].m+=b[i].m; b[fa[i]].v+=++b[i].v; } for (int i=1;i<=n;i++) b[i].c=(b[i].v==n) ? n : min(n,d); for (int i=1;i<=lim;i++)f[i]=INF; for (int i=1;i<=n;i++){ int t=b[i].c; for (int j=0;(1<<j)<=t;j++){ add(f,(ll)b[i].m<<j,(ll)b[i].v<<j); t-=(1<<j); }if (t)add(f,(ll)b[i].m*t,(ll)b[i].v*t); } sort(b+1,b+n+1,cmp); int ans=0; for (int j=0;j<=lim;j++) if (f[j]<=x){ int s=x-f[j],tot=j; for (int i=1;i<=n;i++){ int c=s/b[i].m; if (b[i].v<n)c=min(c,d-b[i].c); tot+=c*b[i].v;s-=c*b[i].m; }ans=max(ans,tot); } printf("%d",ans); return 0; } ```