多重背包-二进制拆分优化
Smiling & Weeping
----难道只有在失眠的晚上,才会想起月亮那片药吗
题目详见:
P1776 宝物筛选 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
总复杂度从O(C * (物品个数之和))降到O(C * (log2(mi)的和))
注意拆分的具体实现,不能全部拆成2的倍数,而是先按2的倍数从小到大拆,最后是一个小于或等于最大倍数。对于mi这样拆分非常有必要,能够保证拆出的数相加在[1 , mi]的范围内,不会大于mi。例如:mi = 25,把它拆成1+2+4+8+10,最后的余数10,10 < 16,读者可以验证用这五个数能够组成1~25的所有数字,不会超过25。
详见代码:
1 #include<bits/stdc++.h> 2 using namespace std; 3 struct data 4 { 5 int value , volume , m; 6 }items[100100]; 7 int dp[100100]; 8 int new_value[100100] , new_volume[100100] , new_m[100100]; //二进制拆分后新物品总数量 9 int main() 10 { 11 int n , w; 12 scanf("%d%d",&n,&w); 13 for(int i = 1; i <= n; i++) 14 scanf("%d%d%d",&items[i].value,&items[i].volume,&items[i].m); 15 int n_new = 0; //二进制拆分后的新物品 16 for(int i = 1; i <= n; i++) 17 { 18 for(int j = 1; j <= items[i].m; j <<= 1) //二进制枚举:1,2,4... 19 { 20 items[i].m -= j; //减去已拆分的 21 new_value[++n_new] = j * items[i].value; 22 new_volume[n_new] = j * items[i].volume; 23 } 24 if(items[i].m) //最后一个余数 25 { 26 new_value[++n_new] = items[i].m * items[i].value; 27 new_volume[n_new] = items[i].m * items[i].volume; 28 } 29 } 30 for(int i = 1; i <= n_new; i++) //枚举物品 31 for(int j = w; j >= new_volume[i]; j--) //枚举背包容量 32 dp[j] = max(dp[j] , dp[j - new_volume[i]] + new_value[i]); 33 cout << dp[w] << endl; 34 return 0; 35 }