题解 P5997 【[PA2014]Pakowanie】

· · 题解

状压DP

第一眼看上去像背包,然而不是的。

首先基础贪心策略:

为了包数最少,肯定是尽量先用大包的。

状态:

$g[i]$表示所有已用背包(即为$i$集合中表示已经用了的)中剩下的**最大空间** $g$存的是剩下的**最大空间**,这样就能满足**上述贪心策略**。 ### 方程: 直接看代码里的方程(**代码中有解释**)吧($i$是集合,$j$是枚举的子集): ``` if(i&(1<<(j-1))){//如果j在子集里,没在就不管他 op=i^(1<<(j-1)); //两种情况,每种情况中还会有两种情况 if(g[op]>=a[j]){//当前物品,不用再开一个包就能装下 。即剩余最大空间满足j物品的大小。 //如果下一个背包大于或等于(等于还需一个条件,另一条件解释在下),更新。 if(f[i]>f[op] || (f[op]==f[i] && g[op]-a[j]>g[i])){//等于情况还加了比较新包装后剩余空间,为合并后的结果(其实也是两种情况),也可以分开写 f[i]=f[op]; g[i]=g[op]-a[j]; } } //剩余空间不够,需要再开一个包( b记录的是包的空间) else if(b[f[op]+1]>=a[j]){//下一个包也要装得下,如果下一个包还装不下就放弃 //这里道理跟上面一样 if(f[i]>f[op]+1 || (f[op]+1==f[i] && b[f[op]+1]-a[j]>g[i])){ f[i]=f[op]+1; g[i]=b[f[op]+1]-a[j]; } } } ``` ### 没了。完整代码: ``` #include<bits/stdc++.h> using namespace std; const int N=(1<<25)+5; int n,m,a[110],b[110],f[N],g[N],maxn; bool cmp(long long x,long long y){ return x>y; } int main(){ cin>>n>>m; for(int i=1; i<=n; i++){ scanf("%d",&a[i]); } for(int i=1; i<=m; i++){ scanf("%d",&b[i]); } maxn=(1<<n)-1; sort(b+1,b+1+m,cmp); for(int i=1; i<=maxn; i++){ f[i]=m+1; } int op; for(int i=0; i<=maxn; i++){ for(int j=1; j<=n; j++){ if(i&(1<<(j-1))){ op=i^(1<<(j-1)); if(g[op]>=a[j]){ if(f[i]>f[op] || (f[op]==f[i] && g[op]-a[j]>g[i])){ f[i]=f[op]; g[i]=g[op]-a[j]; } } else if(b[f[op]+1]>=a[j]){ if(f[i]>f[op]+1 || (f[op]+1==f[i] && b[f[op]+1]-a[j]>g[i])){ f[i]=f[op]+1; g[i]=b[f[op]+1]-a[j]; } } } } } if(f[maxn]>=m+1){ cout<<"NIE"; return 0; } cout<<f[maxn]; } ```