upd:25/10/05
首先要满足题目所给的c_(p_i)≤c_i≤c_(p_i)+D,那么选取一个物品i,就需要将直接或间接依赖于物品i的所有物品都选取一个,由于每个i(i>1)的依赖对象都是唯一的,这形成一个树的结构。
选择i,就是选择了以i为根的子树,这样,问题转化为背包问题:
有n个物品(1<=n<=50),每个物品的重量是w_i(1<=w_i<=1e9* 50),价值是v_i(1<=v_i<=50),除第一个物品外,每个物品的个数限制是D(1<=D<=1e9),第一个物品没有个数限制,背包容量是X(1<=X<=1e9),求最大价值。
解决这个问题需要利用以下交换,将物品按照性价比v_i/w_i排序后,取两个物品i,j(i<j),此时i的性价比>=j的性价比,那么用v_i个j物品,换v_j个i物品,结果不会更差。
理由:价值原本是v_i* v_j,现在是v_j* v_i没有变,重量原来是v_i* w_j现在是v_j* w_i,不会变的更多。
什么时候能执行这种换的操作?当j用了>=v_i个,而i还有>=v_j个没有用的时候,我们放宽这一条件:由于v的范围在[0,50],取定N=50,那么如果j用了>=N个,而i还有>=N个没有用,那么就能执行交换,执行后,j用的数量会减少,i用的数量会增多,如果执行后依旧满足交换条件,继续执行交换,直到不满足,所以最优解可以表示为,任意i,j(i<j),设i选取了c_i个,i最多能选d_i个,那么c_i<=d_i-N且c_j>=N,不成立,也就是c_i>d_i-N或c_j< N。
之后的讨论先将1号物品的数量也限制为D,现在所有d_i都是D。
接下来有一个关键观察, 先来看看D>=N的情况,i从小到大,一旦有一个i,使得c_i<=D-N,那么所有j>i,都需要c_j<N:
在D<N的情况下,所有的c_i都小于N。
由以上分析,最优解应该具备这样的结构:选一个k作为转折点,第1到k-1的c_i>D-N,c_k<=D-N,k+1到n的c_i<N。(k从0取到n+1)。
第1到(k-1)种物品D-N的部分取满,第k种物品D-N的部分取一部分,下图其余蓝色部分取<=N。
由以上结构导出以下简洁明快的算法:将d_i小于等于N的部分装入A袋,大于N的部分装入B袋,此时A袋的总价值较小,对每一个总价值用二进制拆分的分组背包求最小重量,遍历每个总价值,得到剩余的容量,这些部分从1号物品开始,尽量多装B袋的物品。
那么再来考虑可以无限取的1号物品:如果遇到1号物品,由于1号物品可以无限取,所以其后物品的c_i全部小于N,所以1号物品同样是装的越多越好,以上算法仍然奏效。
广义化
对于物品数量较少,物品价值较小的分组背包问题,将物品按照性价比从高到低排序,取N=max(v_i),将d_i小于等于N的部分装入A袋,大于N的部分装入B袋,此时A袋的总价值较小,对每一个总价值用二进制拆分的分组背包求最小重量,遍历每个总价值,得到剩余的容量,这些剩余容量从1号物品开始,依次尽量多装B袋的物品。
/*
模板符合C++98标准
upd:25.09.06
*/
//输入输出相关头文件
#include<iostream>
#include<cstdio>
//数据类型和STL
#include<cstring>
#include<string>
#include<array>
#include<cctype>
#include<set>
#include<map>
#include<unordered_map>
#include<queue>
#include<vector>
#include<bitset>
#include<stack>
#include<utility>
//其他
#include<cmath>
#include<algorithm>
#include<cstdlib>
#include<iomanip>
#include<climits>
#include<limits>
#include<sstream>
using namespace std;
typedef long long ll;
typedef unsigned long ul;
typedef unsigned long long ull;
#define fastio ios::sync_with_stdio(0);cin.tie(0);
#define fin freopen("D:/in.txt","r",stdin);
#define fout freopen("D:/out.txt","w",stdout);
const ll N=50,maxn=50+5,inf=1e12,maxv=125*1e3+5,V=125*1e3;
ll n,x,d;
ll m[maxn],p[maxn],a[maxn],b[maxn],minw[maxv];
vector<ll> e[maxn];
struct Item{
ll v,w,id;
bool operator < (const Item &rhs) const {
return v*rhs.w>rhs.v*w;
}
}item[maxn];
pair<ll,ll> dfs(ll u) {
ll val=1,weight=m[u];
for(ll v:e[u]) {
ll childval=0,childweight=0;
tie(childval,childweight)=dfs(v);
val+=childval;
weight+=childweight;
}
item[u].v=val;
item[u].w=weight;
item[u].id=u;
return {val,weight};
}
int main()
{
cin>>n>>x>>d;
for(ll i=1;i<=n;i++) {
if(i==1) cin>>m[i];
else {
cin>>m[i]>>p[i];
e[p[i]].emplace_back(i);
}
}
dfs(1);
stable_sort(item+1,item+1+n);
//TODO:输出物品w和v
/*for(ll i=1;i<=n;i++) {
printf("item%lld w=%lld v=%lld id=%lld\n",i,item[i].w,item[i].v,item[i].id);
}*/
//分a,b两袋
for(ll i=1;i<=n;i++) {
if(item[i].id==1) {
a[i]=N;
b[i]=inf;
}else {
if(d>N) {
a[i]=N;
b[i]=d-N;
}else {
a[i]=d;
b[i]=0;
}
}
}
for(ll i=0;i<=V;i++) minw[i]=inf;
minw[0]=0;
for(ll i=1;i<=n;i++) {
//对a[i]作二进制拆分作为单个物品
ll t=a[i],curv=0,curw=0;
for(ll k=0;t>0;k++) {
if(t>(1LL<<k)){
t-=(1LL<<k);
curv=(1LL<<k)*item[i].v,curw=(1LL<<k)*item[i].w;
//printf("物品%lld拆分出curv=%lld curw=%lld\n",i,curv,curw);
}else {
curv=t*item[i].v,curw=t*item[i].w;
//printf("物品%lld拆分出curv=%lld curw=%lld\n",i,curv,curw);
t=0;
}
for(ll j=V;j>=0;j--) {
if(j>=curv) {
minw[j]=min(minw[j],minw[j-curv]+curw);
}
}
}
}
ll ans=0;
for(ll i=0;i<=V;i++) {
ll restw=x-minw[i],value=i;
if(restw>=0) {
//TODO
// printf("价值%lld可用A袋达成,使用重量%lld\n",value,minw[value]);
//从一号物品开始贪心地选
for(ll j=1;j<=n;j++) {
if(restw>=(__int128)b[j]*item[j].w) {
value+=b[j]*item[j].v;
restw-=b[j]*item[j].w;
//printf("选了%lld个排序后的%lld号\n",b[j],j);
}else {
//能选几个是几个
ll cnt=restw/item[j].w;
value+=cnt*item[j].v;
//printf("选了%lld个排序后的%lld号\n",cnt,j);
break;
}
}
ans=max(ans,value);
}
}
cout<<ans<<"\n";
return 0;
}