洛谷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%\) 的数据,\(n\leq \sum\) \(m_i\leq 10^4\)\(0\le W\leq 10^3\)
对于 \(100%\) 的数据,\(n\leq \sum\) \(m_i \leq 10^5\)\(0\le W\leq 4\times 10^4\)\(1\leq n\le 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\n",f[m]);
	return 0;
}
posted @ 2020-08-06 14:45  徐明拯  阅读(177)  评论(0编辑  收藏  举报