问题描述
蒜头君酷爱搭积木,他用积木搭了 n 辆重量为 wi的小车和一艘最大载重量为 W 的小船,他想用这艘小船将 n 辆小车运输过河。每次小船运载的小车重量不能超过 W。另外,小船在运载小车时,每辆小车会对小船有一个损坏值si,当多辆小车一起运载时,该趟运载对小船的损坏值为船上所有小车的最大损坏值。
现在蒜头君想知道,如何用小船运载 n 辆小车,可以使得对小船造成的总损坏值最小。
输入格式
第一行输入两个数 W 和 n(100≤w≤400,1≤n≤16),分别表示小船的最大载重量和小车总数。
接下来输入 n 行,每行输入两个整数si和 wi(1≤si ≤50,10≤wi≤100),分别表示每辆小车对小船的损坏值和每辆小车的重量。
输出格式
输出一行,输出一个整数,表示用小船运载 n 辆小车,最小的总损坏值。
样例输入
90 4
32 50
15 20
40 50
13 40
样例输出
72
数据处理:
将船只按照花费从小到大排序。每艘船已经运过去设为1,没运过去设为0,则每种状态为一个二进制数,我们所要达到的状态即为(1<<n)-1(注意这个状态数)。
转移方程:
从1到(1<<n)-1循环状态cur。对cur,计算其已经运过去的重量和sum。若sum<=w,则可一次性运过去,其花费即损失值 的最大值即dp[cur]=max(cost[i])。若sum>w,则将其分成两个子集a和b(a|b=cur),因为a与b为子集所以在之前已经计算过所以有dp[cur]=min(dp[a]+dp[b])。
技巧:
for(int i=1;i<1<<n;i++)
for(int j=i;j;j=(j-1)&i)
dp[i]=min(dp[i],dp[j]+dp[j^i]);
代码
#include<bits/stdc++.h>
using namespace std;
const int N=50; const int MAX=1000000;
int n,w,sum,maxx,cost[N],weight[N],dp[1<<17];
int main(){
scanf("%d %d",&w,&n);
for(int i=1;i<=n;i++)
scanf("%d %d",&cost[i],&weight[i]);
for(int i=1;i<(1<<n);i++){
sum=0; maxx=0;
for(int cur=i,j=1;cur;j++,cur=cur>>1)//计算当前状态的重量即一次性运过去的花费
if(cur&1) {sum+=weight[j]; maxx=max(maxx,cost[j]);}
if(sum<=w) dp[i]=maxx;
else{
dp[i]=MAX;
for(int j=i;j;j=(j-1)&i){
dp[i]=min(dp[i],dp[j]+dp[j^i]);
}
}
}
printf("%d",dp[(1<<n)-1]);
return 0;
}