Sweet Alchemy_arc096F分析与解答

54 阅读5分钟

upd:25/10/05

首先要满足题目所给的c_(p_i)≤c_i≤c_(p_i)+D,那么选取一个物品i,就需要将直接或间接依赖于物品i的所有物品都选取一个,由于每个i(i>1)的依赖对象都是唯一的,这形成一个树的结构。

picture does not exist

选择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:

image.png

在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。

image.png

由以上结构导出以下简洁明快的算法:将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;  
}