对应POJ题目:点击打开链接
有n件家具和两辆车(容量分别为c1, c2),某人要搬家,问至少需要几次才能把所有家具运完(两辆车必须同时行驶)。
思路:
n很小,可以用二进制保存1 ~ (1<<n) - 1 种状态,每种状态表示几件家具,比如n = 7时。5这个状态,5用二进制表示为 0000101,可以用它表示为第5件家具和第7件家具被选上(或者从右边数起,表示第1和第3件家具)。之后可以枚举每种状态i,之后用两个循环就可完成。转移方程为:dp[i | j] = min{dp[i | j], dp[i] + 1}; (j表示j状态能否一次被两辆车运走,其中i & j 要为空)。dp[x] 表示x状态至少需要几次才能拉走,所以dp[(1<<n) - 1] 就是答案。
再来看怎样求一个状态x,使它能一次被两辆车运走 。先根据状态x的二进制中为1的位置进行对应家具求和sum,然后枚举x的子状态,同样据子状态的二进制中为1的位置进行对应家具求和sum1(表示此sum1要被两辆车中的某辆一次拉走),sum2 = sum - sum1。如果sum1和sum2都符合载重。则状态x能一次被两辆车运走。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define N 10
#define M (1<<N)
#define INF (1<<30)
#define MIN(x, y) ((x) < (y) ? (x) : (y))
#define SIZE ((1<<n)-1)
int a[N];
int dp[M];
int OK[M];
int c1, c2;
bool Judge(int x)
{
int i, j, t = 0;
int sum1 = 0, sum2 = 0, sum = 0;
for(i=1; i<=x; i=(1<<t)){ /* 对x集合所包含的物品求和 */
if(i & x) sum += a[t];
t++;
}
if(sum > c1 + c2) return 0;
for(i=1; i<=x; i++){ /* 枚举x的子集 */
if((i & x) != i) continue; /* i不是x的子集 */
sum1 = t = 0;
for(j=1; j<=i; j=(1<<t)){
if(j & i) sum1 += a[t];
t++;
}
sum2 = sum - sum1;
if(sum1 <= c1 && sum2 <= c2) return 1;
if(sum2 <= c1 && sum1 <= c2) return 1;
}
return 0;
}
int main()
{
//freopen("in.txt", "r", stdin);
int T, n, w = 0;
int i, j, ans;
scanf("%d", &T);
while(T--)
{
memset(OK, 0, sizeof(OK));
scanf("%d%d%d", &n, &c1, &c2);
for(i=0; i<n; i++) scanf("%d", a + i);
for(i=1; i<M; i++) dp[i] = INF;
for(i=1; i<=SIZE; i++)
if(Judge(i)) OK[i] = dp[i] = 1;
for(i=1; i<=SIZE; i++){
for(j=1; j<=SIZE; j++){
if(i & j) continue; /* 交集不为空 */
if(dp[i] != INF && OK[j])
dp[i|j] = MIN(dp[i|j], dp[i] + 1);
}
}
printf("Scenario #%d:\n%d\n", ++w, dp[SIZE]);
if(T) printf("\n");
}
return 0;
}