洛谷P1776 宝物筛选(二进制优化多重背包)

题目描述

终于,破解了千年的难题。小 (FF) 找到了王室的宝物室,里面堆满了无数价值连城的宝物。
这下小 (FF) 可发财了,嘎嘎。但是这里的宝物实在是太多了,小 (FF) 的采集车似乎装不下那么多宝物。看来小 (FF) 只能含泪舍弃其中的一部分宝物了。
(FF) 对洞穴里的宝物进行了整理,他发现每样宝物都有一件或者多件。他粗略估算了下每样宝物的价值,之后开始了宝物筛选工作:小 (FF) 有一个最大载重为 (W) 的采集车,洞穴里总共有 (n) 种宝物,每种宝物的价值为 (v_i),重量为 (w_i),每种宝物有 (m_i)件。小 (FF) 希望在采集车不超载的前提下,选择一些宝物装进采集车,使得它们的价值和最大。

输入格式

第一行为一个整数 (n)(W),分别表示宝物种数和采集车的最大载重。
接下来 (n) 行每行三个整数 (v_i,w_i,m_i)

输出格式

输出仅一个整数,表示在采集车不超载的情况下收集的宝物的最大价值。

输入输出样例

输入 #1

4 20
3 9 3
5 9 1
9 4 2
8 1 3

输出 #1

47

说明/提示
对于 (30%) 的数据,(nleq sum) (m_ileq 10^4)(0le Wleq 10^3)
对于 (100%) 的数据,(nleq sum) (m_i leq 10^5)(0le Wleq 4 imes 10^4)(1leq nle 100)

思路

学过小学奥数的应该都知道,(2^n)以内的数(2)的幂可以通过加和通过得到(2^{n+1})以内的所有数,那么就可以利用这个特殊的性质来加速多重背包和完全背包。原本的多重背包需要三层循环一个个物品枚举,这样是立方复杂度,非常慢。而优化之后就是平方再乘以对数,相当于是平方再乘了个比较大的常数,可以大大降低时间复杂度。那么具体怎么优化呢?因为可以选多个一样的物品,所以我们就可以合并其中的多个,来表示所有可以取到的可能性,转化为01背包来做,具体看代码加注释。

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<queue>
#include<cstring>
using namespace std;
int n,m,cnt;
int v,v1[100005],w,w1[100005],f[100005],s;
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%d%d%d",&v,&w,&s);//因为我们将每种物品转化为了01背包来做,所以不用开数组存储,可以节省空间
		int p=0;
		while(1){
			int t=pow(2,p);//二进制优化
			if(t<s){//如果还能接着拆,那么相当于将几个物品合并为了一个,同时也不影响可以枚举到所有的情况
				s-=t;
				v1[++cnt]=v*t;
				w1[cnt]=w*t;
				p++;
			}
			else{//如果不能拆了,比如现在还剩3个物品,但是t=8,那么就直接将这个加进去即可,以表示所有的可能,同时别忘记结束循环
				v1[++cnt]=v*s;
				w1[cnt]=w*s;
				break;
			}
		}
	}
	for(int i=1;i<=cnt;i++){
		for(int j=m;j>=w1[i];j--){
			f[j]=max(f[j],f[j-w1[i]]+v1[i]);//01背包
		}
	}
	printf("%d
",f[m]);
	return 0;
}
原文地址:https://www.cnblogs.com/57xmz/p/13446267.html