题解:洛谷 P5997 [PA 2014] Pakowanie
题意
你有 \(n\) 个物品和 \(m\) 个背包,物品有各自的质量且不可被分割,包也有各自容量。求装完所有物品所需要的最小背包数量。
思路
看到背包和物品,我们很容易想到背包问题,但这道题背包容量太大了,根本开不下这么大的数组。于是我们继续观察数据,发现 \(n\le24\),而每个物品都只有放了或没放两种情况,意味着总状态数最大为 \(2^{24}=16777216\),那么这就是一个状压 dp 问题了。
既然确定了总方法,我们就来继续分析题目。首先容易发现一个贪心思路:先放满大的背包,再放小的,即需要按照背包容量进行降序排序。
然后就要进行状态转移,为了记录答案,我们首先用 \(f_k\) 记录放置 \(k\) 中的物品需要用到的背包数量。依照上面的贪心,我们在大的背包没放满前不能开小的背包,所以还需要用一个 \(g_k\) 表示在当前状态下,剩余容量最大的背包的剩余容量。
接下来思考转移条件。容易发现,对于某个物体,如果当前背包能放下,那么就放进去;如果在目前开的这些背包里剩余容量最大的背包放不下,就必须要新开一个背包,而新开的这个背包如果也放不下,由于我们是降序排序背包容量,则这个物品根本放不下,就不需要更新答案。对于能放下的情况,我们依然要进行进一步判断,我们希望背包数量尽可能少,若背包数量相同,为了多放物品,也需要背包剩余容量尽可能多,那么背包数量变少或者剩余背包最大容量变大的情况可以更新答案。
所以整体思路就有了:先降序排序背包,再依次枚举放哪个物品,依照转移条件更新答案。
这里无解也是很容易判断的。
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n,m;
int a[30],c[110];
int f[16777216],g[16777216];
bool cmp(int x,int y){
return x>y;
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=m;i++)cin>>c[i];
for(int i=1;i<(1<<n);i++)f[i]=111;//初始化,我们这里默认了f[0]=0
sort(c+1,c+m+1,cmp); //降序排序背包容量
for(int i=0;i<(1<<n);i++){ //枚举状态
for(int j=1;j<=n;j++){ //枚举放哪个物品
if(!((i>>(j-1))&1))continue; //这里是因为我们必须保证在状态i下是放了物品j的
int k=i^(1<<(j-1)); //这个式子表示i中去掉j不放的状态
if(g[k]>=a[j]){//如果背包容量足够,不用开新的
if(f[i]>f[k]||f[i]==f[k]&&g[i]<g[k]-a[j]){//背包数量变少或者剩余背包容量变大
f[i]=f[k];//更新答案
g[i]=g[k]-a[j];
}
}else{//需要开新的背包
if(c[f[k]+1]>=a[j]){//这里是判断新的背包能否放下,如同之前分析,如果新的背包也放不下那么这个物体就放不下了
if(f[i]>f[k]+1||f[i]==f[k]+1&&g[i]<c[f[k]+1]-a[j]){//这里原理与上面一样,不过由于新开了一个背包,所以要加一
f[i]=f[k]+1;//更新答案
g[i]=c[f[k]+1]-a[j];
}
}
}
}
}
if(f[(1<<n)-1]==111)cout<<"NIE";
else cout<<f[(1<<n)-1];
return 0;
}

浙公网安备 33010602011771号