题意
给出 n n n 个物品,体积为 w 1 , w 2 , ⋯ , w n w _ 1, w _ 2, \cdots, w _ n w1,w2,⋯,wn,现把其分成若干组,要求每组总体积小于等于 W W W,问最小分组数量。
n ≤ 18 , 1 ≤ c i ≤ W ≤ 1 0 8 n\le 18,1\le c_i\le W\le 10^8 n≤18,1≤ci≤W≤108。
样例输入 #1
4 10
5
6
3
7
样例输出 #1
3
提示
样例中可通过 ( 5 ) ( 6 ) ( 3 , 7 ) (5)(6)(3,7) (5)(6)(3,7) 方式分成 3 组
思路
考虑记 d p i , g i dp_i,g_i dpi,gi 为在当前状态下最少分组数和最大空间剩余数, j j j 为 i i i 状态下已经分组完的人, l a s t = i − 2 j − 1 last = i - 2^{j-1} last=i−2j−1,表示除了 j j j以外所有人的状态,则有:
-
d p i = d p l a s t , g i = g l a s t − c j , g i ≥ c j dp_i = dp_{last},g_i = g_{last}-c_j,g_i \geq c_j dpi=dplast,gi=glast−cj,gi≥cj
-
d p i = d p l a s t + 1 , g i = max ( g l a s t , w − c j ) , o t h e r w i s e dp_i = dp_{last} + 1,g_i = \max(g_{last},w - c_j),otherwise dpi=dplast+1,gi=max(glast,w−cj),otherwise
以上两种状态分别对应着塞进原有的组,和新分一组的方案数。
根据以上方程转移即可。
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,w,c[20];
int dp[(1 << 18) + 1],g[(1 << 18) + 1];
signed main() {
scanf("%lld %lld",&n,&w);
for(int i = 1;i <= n;i++) scanf("%lld",&c[i]);
for(int i = 1;i < (1 << n);i++) {
dp[i] = 1e18;
for(int j = 1;j <= n;j++) {
if(!(i & (1 << (j - 1)))) continue;
int last = i - (1 << (j - 1));
if(g[last] >= c[j] and dp[i] >= dp[last]) {
dp[i] = dp[last],g[i] = max(g[i],g[last] - c[j]);
}
else {
if(dp[last] + 1 <= dp[i]) {
dp[i] = dp[last] + 1,g[i] = max(max(g[i],g[last]),w - c[j]);
}
}
}
}
printf("%lld\n",dp[(1 << n) - 1]);
return 0;
}