題意:給你汽車容積c1,c2,再給你n個包裹的體積,問你最少運幾次能全運走
思路:用2進製表示每次運送時某物在不在此次運送之中,1在0不在。我們把運送次數抽象成物品價值,把狀態抽象成體積,用一個dp[ i ] 記錄完成狀態i的最少步數那麼就轉化爲了01揹包問題,得到狀態轉移方程dp[ j|state ] = min( dp[ j|state ],dp[j] + 1 ),state爲運送時物品的狀態。
然後講一下可能會有點看不懂的judge()的一段代碼
for(int j = c1;j >= val[i];j--){ //將所有可能放進c1的組合標記爲1
if(vis[j - val[i]])
vis[j] = 1;
}
這裏的意思是將所有能放進c1的組合標記爲1,他是這樣運作的:先將vis[0] = 1,這樣每次j到val[i]時,val[i]肯定會被置爲1,因爲是從c1開始往下搜,如果搜到一個vis[j - val[i]] = 1,這說明 j-val[i] 和 val[i] 能做成一個體積爲j(j<=c1)的組合,可見j能塞進c1,所以置爲1,這樣一直搜索就能搜到所有的組合
代碼:
#include<cstdio>
#include<map>
#include<set>
#include<queue>
#include<cstring>
#include<string>
#include<cmath>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define ll long long
const int maxn = 1 << 10;
const int MOD = 100000000;
const int INF = 0x3f3f3f3f;
using namespace std;
int val[12],state[maxn],vis[maxn]; //vis[i] = 1代表重量爲i的組合能塞進車c1
int dp[maxn]; //達成i狀態最小步驟
int tot,n,c1,c2;
int judge(int x){ //判斷能否一次運走
int sum = 0;
memset(vis,0,sizeof(vis));
vis[0] = 1;
for(int i = 0;i < n;i++){
if(x&1<<i){
sum += val[i];
for(int j = c1;j >= val[i];j--){
//將所有可能放進c1的組合標記爲1
if(vis[j - val[i]])
vis[j] = 1;
}
}
}
if(sum > c1+c2) return 0; //總體積大於兩車總容積
for(int i = 0;i <= c1;i++){
if(vis[i] && sum - i <= c2){
//只要有一種分組能讓兩輛車都能塞進兩種組合
return 1;
}
}
return 0;
}
void init(){ //初始化,找到所有能運送的狀態
tot = 0;
for(int i = 1;i < (1<<n);i++){
dp[i] = INF;
if(judge(i)){
state[tot++] = i;
}
}
}
int main(){
int T,Case = 1;
scanf("%d",&T);
while(T--){
scanf("%d%d%d",&n,&c1,&c2);
for(int i = 0;i < n;i++)
scanf("%d",&val[i]);
init();
int V = (1<<n) - 1;
dp[0] = 0;
for(int i = 0;i < tot;i++){
for(int j = V;j >= 0;j--){
if(dp[j] == INF) continue;
if(j&state[i]) continue;
//有交集,不能送第二次
dp[j|state[i]] = min(dp[j|state[i]],dp[j] + 1);
}
}
printf("Scenario #%d:\n%d\n\n",Case++,dp[(1<<n) - 1]);
}
return 0;
}