多重背包-二进制拆分优化

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 }

 

posted @ 2023-07-01 00:29  smiling&weeping  阅读(78)  评论(0)    收藏  举报