P9140 [THUPC 2023 初赛] 背包
题意
有 \(n\) 个物品,体积 \(v_i\) 价值 \(w_i\),每个物品可被多次取。\(q\) 次询问背包容量 \(V\),求是否存在恰好装满背包的方案,若有解输出价值和的最大值。
\(n\le 50,q\le 10^5,v_i\le 10^5,w_i\le 10^6,10^{11}\le V\le 10^{12}\)
分析
这种容量极大的特殊背包问题一般考虑按照贪心策略(性价比)先一顿狂取再考虑别的。假设性价比最大的物品为 \(p\),其体积为 \(m\),考虑同于最长路,建出 \(0\sim m-1\) 这 \(m\) 个点表示背包容量体积模 \(m\) 为 \(i\),转移就是往里面添加物品,\(i\rightarrow (i+v_j)\bmod m\),由于这 \(m\) 个点不能表示出体积除 \(m\) 下取整的结果,我们考虑每走过一整个 \(m\) 就将边权减掉 \(w_p\),所以边权为 \(w_i-\lfloor\frac{v_i+p}{m}\rfloor\)(相当于费用提前计算了)。最终询问 \(V\) 的答案就是 \(\lfloor\frac{V}{m}\rfloor w_p+f_{V\bmod m}\)。
注意 \(p\) 取的数量越多价值越大这个结论是错的。
由于是模意义下的完全背包问题,可以写转圈。具体而言,对于一件物品,其连成的边会形成 \(\gcd(m,v_i)\) 个环,而在这个环上直接做 dp 显然是错的,但是我们可以通过做两遍 dp 保证所有起点都被枚举了一遍。单次转移复杂度是 \(O(m)\) 的,因此总复杂度是确定性 \(O(nm)\)。
int n,Q,m,v[maxn],w[maxn];
int f[maxn];
int vis[maxn];
inline void solve_the_problem(){
n=rd(),Q=rd();
rep(i,1,n)v[i]=rd(),w[i]=rd();
int r=1;
rep(i,2,n)if(w[i]*v[r]>w[r]*v[i])r=i;
if(r^1)swap(v[r],v[1]),swap(w[r],w[1]);
mem(f,~0x3f);
f[0]=0,m=v[1];
rep(i,2,n){
rep(j,0,m-1)vis[j]=0;
rep(j,0,m-1)if(!vis[j]){
for(int p=j,nxt;vis[p]<=1;++vis[p],p=nxt){
nxt=(p+v[i])%m;
ckmx(f[nxt],f[p]+w[i]-(p+v[i])/m*w[1]);
}
}
}
while(Q--){
int x=rd();
if(f[x%m]<-1e18){PW;continue;}//f[x%m]可能为负
write(f[x%m]+x/m*w[1]);
}
}

浙公网安备 33010602011771号