原题链接
- 多重背包,装满,w[i] == v[i]
- 因为是第一次做多重背包,我首先试图将所有可能装出的重量枚举出来,TLE。代码如下:
//1000MS(TLE) 1520K
#include <iostream>
#include <cstring>
using namespace std;
const int maxn = 10;
const int maxw = 120005;
bool ok[maxw];
bool la[maxw];
int n[maxn];
int main(){
int t=0;
while(++t){
memset(ok,0,sizeof(ok));
ok[0] = true;
bool suc;
int sum = 0;
for(int i=1;i<=6;i++){
cin>>n[i];
sum += i * n[i];
}
if(!sum) break;
if(sum%2)
suc = false;
else{
int W = sum/2;
for(int i=1;i<=6;i++){
for(int ii=0;ii<=W;ii++)
la[ii] = true;//是上一层可达到的
for(int j=0;j<=W;j++){
if(ok[j] && la[j]){
for(int k=1;k<=n[i];k++){
if(j + i*k <= W)
if(ok[j + i*k]) continue;
else{
ok[j + i*k] = true;
la[j + i*k] = false;
}
else break;
}
}
}
}
suc = ok[W];
}
cout<<"Collection #"<<t<<':'<<endl;
if(suc)
cout<<"Can be divided."<<endl;
else
cout<<"Can't be divided."<<endl;
}
return 0;
}
- 接下来,根据多重背包的特点讲解本题(多重背包一般不需要使用 O(V*N) 的算法,这里也仅采用将其二进制拆分,转化成 01背包 的方法解题)
- 首先,多重背包与完全背包的二进制拆分有所不同,这是由拆分目的决定的:
- 对完全背包来说,我们需要拆分出若干物体,使背包重量在【0,W】区间内的所有值均可以取到,因此,我们需要将系数 k 从 0 拆到满足 2^k <= W < 2^(k+1) 的所有物品,详见我的另一篇Blog完全背包。
- 对多重背包来说,我们需要拆分出若干物体,满足这些物体既可以组合成【0,n[i]】个物体,又绝不能组合出多于 n[i] 个物品,初学时,结合这两种算法的异同去思考如何拆分是很有趣的。推荐你起身走走,自言自语思考一番。
void pack(){
//二进制转 01 背包(多重背包版)
t = 1;
for(int i=1;i<=6;i++){
for(int k=1;n[i];k<<=1){
if(k > n[i])
k = n[i];
w[t++] = i * k;
n[i] -= k;
}
}
return ;
}
- 其次,本题包含装满的要求,因此初始化时要用到 MIN 这一无穷小量(关于无穷小量的选取,可移步Here),并且 dp[0] = 0。
memset(dp,MIN,sizeof(dp));
dp[0] = 0;
-
完整代码(二进制拆分转化01背包,判断装满,质价相同:421MS 1872K)
-
注意:应有两种初始化及判断装满的方法,但是注释行的那种会 WA ,尚不知为何。
//421MS 1872K
#include <iostream>
#include <cstring>
#define INF 0x3f3f3f3f
#define MIN 0xc0c0c0c0
using namespace std;
const int maxn = 100;
const int maxw = 120005;
int dp[maxw];
int w [maxn];
int n[10];
bool suc;
int sum;
int W;
int t;
void init(){
sum = 0;
suc = true;
return ;
}
void input_judge(){
for(int i=1;i<=6;i++){
cin>>n[i];
sum += i * n[i];
}
if(sum%2) suc = false;
return ;
}
void pack(){
//二进制转 01 背包(多重背包版)
t = 1;
for(int i=1;i<=6;i++){
for(int k=1;n[i];k<<=1){
if(k > n[i])
k = n[i];
w[t++] = i * k;
n[i] -= k;
}
}
return ;
}
void deal(){
pack();
W = sum/2;
//至此,问题已经转化成了使用 01 背包思想将背包装满,并且物品重量 == 物品价值
memset(dp,MIN,sizeof(dp));
dp[0] = 0;
for(int i=1;i < t;i++){
for(int j=W;j >= w[i];j--){
//if(dp[j - w[i]] + w[i] > 0)
dp[j] = max(dp[j] , dp[j - w[i]] + w[i]);
}
}
if(dp[W] > 0)//if(dp[W] != INF)
suc = true;
else
suc = false;
return ;
}
void output(){
if(suc)
cout<<"Can be divided."<<endl;
else
cout<<"Can't be divided."<<endl;
return ;
}
int main(){
int t=0;
while(++t){
init();
input_judge();
if(!sum)
break;
if(suc)
deal();
cout<<"Collection #"<<t<<':'<<endl;
output();
cout<<endl;
}
return 0;
}

本文深入解析了多重背包问题,通过对比完全背包与多重背包的二进制拆分,阐述了多重背包的特性与解决策略。文章提供了从TLE到AC的代码优化过程,包括二进制拆分转化为01背包的实现,以及如何判断背包是否能被装满。
473

被折叠的 条评论
为什么被折叠?



