【计蒜客习题】蒜头君的积木
问题描述
蒜头君酷爱搭积木,他用积木搭了 n 辆重量为 wi的小车和一艘最大载重量为 W 的小船,他想用这艘小船将 n 辆小车运输过河。每次小船运载的小车重量不能超过 W。另外,小船在运载小车时,每辆小车会对小船有一个损坏值si,当多辆小车一起运载时,该趟运载对小船的损坏值为船上所有小车的最大损坏值。
现在蒜头君想知道,如何用小船运载 n 辆小车,可以使得对小船造成的总损坏值最小。
输入格式
第一行输入两个数 W 和 n(100≤w≤400,1≤n≤16),分别表示小船的最大载重量和小车总数。
接下来输入 n 行,每行输入两个整数si和 wi(1≤si ≤50,10≤wi≤100),分别表示每辆小车对小船的损坏值和每辆小车的重量。
输出格式
输出一行,输出一个整数,表示用小船运载 nn 辆小车,最小的总损坏值。
样例输入
90 4
32 50
15 20
40 50
13 40
样例输出
72
状压DP里的经典做法,枚举子集。
for(int j=0;j<(1<<n);++j) for(int k=j;k;k=(k-1)&j)
仔细揣摩一下上面的代码,这样枚举子集的效率是很高的。请记住!
对于这道题很容易想到,对于要求解的任意一个方案(第i辆搬不搬),可以通过其子集求解,我们枚举其子集代表达成此方案的最后一次运载,取补集就是之前完成的,这样dp[j]=min{dp[j-k]+cost[k]}。为了提高效率我们可以先预处理出每种情况的花费。
1 #include<cstdio> 2 #include<cstring> 3 inline int min(int a,int b) {return a<b?a:b;} 4 inline int max(int a,int b) {return a>b?a:b;} 5 const int maxw=405,maxn=20,inf=0x3f3f3f3f; 6 int W,n,s[maxn],w[maxn],dp[1<<maxn],weight[1<<maxn],cost[1<<maxn]; 7 int get_w(int i) { 8 int ans_w=0; 9 for(int j=1;j<=n;++j) { 10 if(i&1) ans_w+=w[j]; 11 i>>=1; 12 } 13 return ans_w; 14 } 15 int get_c(int i) { 16 int ans_c=0; 17 for(int j=1;j<=n;++j) { 18 if(i&1) ans_c=max(ans_c,s[j]); 19 i>>=1; 20 } 21 return ans_c; 22 } 23 int main() { 24 scanf("%d%d",&W,&n); 25 for(int i=1;i<=n;++i) scanf("%d%d",&s[i],&w[i]); 26 memset(dp,inf,sizeof(dp)); 27 for(int i=0;i<(1<<n);++i) weight[i]=get_w(i),cost[i]=get_c(i); 28 dp[0]=0; 29 for(int j=1;j<(1<<n);++j) { 30 for(int k=j;k;k=(k-1)&j) { 31 if(weight[k]<=W) 32 dp[j]=min(dp[j],dp[j-k]+cost[k]); 33 } 34 } 35 printf("%d",dp[(1<<n)-1]); 36 return 0; 37 }