ARC096F Sweet Alchemy
ARC096F
一棵树,每个节点有个权值\(m_i\)。
你要给每个点钦定一个非负整数\(c_i\),要求:\(c_{fa_{i}}\le c_i\le c_{fa_{i}}+d\)
并且满足\(\sum m_ic_i\le x\)。
最大化\(\sum c_i\)。
\(n\le 50\)
\(x,d\le 10^9\)
显而易见首先差分一下,就变成了一个背包问题:物品的代价为子树中\(m_i\)的和,价值为子树大小。每个物品可以取最多\(d\)个(除了根节点之外)。
现在记价值为\(v_i\),代价为\(w_i\)。
有个十分错误的贪心:以\(\frac{v_i}{w_i}\)从大到小排序,然后贪心地选取。
考虑如何调整这个贪心做法:假如有\(\frac{v_i}{w_i}>\frac{v_j}{w_j}\),如果选超过\(v_i\)个\(j\),自然不如选\(v_j\)个\(i\)(如果这个时候还有\(v_j\)个\(i\))。这时候就可以调整一下。
于是每个物品个数就取\(\min(n,d)\)个做多重背包,然后剩下的贪心即可。
using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 55
#define ll long long
#define INF 1000000000
int n,x,d;
int p[N];
ll w[N],v[N];
ll f[N*N*N];
int q[N];
bool cmpq(int a,int b){return (ll)v[a]*w[b]>(ll)v[b]*w[a];}
int main(){
// freopen("in.txt","r",stdin);
scanf("%d%d%d%lld",&n,&x,&d,&w[1]),v[1]=1;
for (int i=2;i<=n;++i)
scanf("%lld%d",&w[i],&p[i]),v[i]=1;
for (int i=n;i>=2;--i){
w[p[i]]+=w[i];
v[p[i]]+=v[i];
}
int c=min(n,d);
memset(f,63,sizeof f);
f[0]=0;
for (int i=1;i<=n;++i){
ll w_=w[i],v_=v[i];
int r=c;
for (int lg=0;r;++lg){
w_=w[i]*min(1<<lg,r);
v_=v[i]*min(1<<lg,r);
r-=min(1<<lg,r);
for (int k=n*c*i;k>=v_;--k)
f[k]=min(f[k],f[k-v_]+w_);
}
}
d-=c;
for (int i=1;i<=n;++i)
q[i]=i;
sort(q+1,q+n+1,cmpq);
ll ans=0;
for (int j=0;j<=n*n*n;++j){
if (f[j]>x) continue;
ll r=x-f[j],s=j;
for (int i=1;i<=n;++i){
ll t=min(r/w[q[i]],q[i]==1?(ll)INF:d);
r-=w[q[i]]*t;
s+=v[q[i]]*t;
}
// printf("%d %lld %lld\n",j,f[j],s);
ans=max(ans,s);
}
printf("%lld\n",ans);
return 0;
}