传送门:Corns
标签:动态规划
题目大意
给定n个物品,每个物品的价值为ai,其占用的空间也为ai,给出一个大小为W的背包,你需要在不超过背包容量的前提下将总价值最大化。请你输出这个最大价值。
输入:两个正整数n,W(1<=n,W<=2e5),然后n个正整数a1,a2,······,an(1<=a1+a2+···+an<=2e5)。
输出:一个正整数,代表能够获得的最大价值。
算法分析
- 看完题目就觉得是背包问题,典中典,但背包的时间复杂度是O(n2),在2e5的数据范围下显然是不适用的。我们发现 这题中物品的价值和空间是相同的,也就是说每个物品的“性价比”都是一样的。那么就将同等空间求最大价值的问题转化为了如何尽可能地填满一个背包。对于这个数据范围我们自然而然地会想到时间复杂度要带个log,那么究竟是什么算法呢?
- 这里要引入多重背包的概念。我们知道0/1背包问题中每种物品只有一个,而在完全背包中每种物品都有无限多个。二者的区别在于遍历w时一个是从后往前,一个是从前往后。多重背包是这两个问题的结合,给出n种物品,第i种物品有ki件。这时我们可以枚举每种物品拿的数量,然后当成0/1背包来做;也可以在完全背包中加一个判断,防止取的数量超过ki。但这两种方法的时间复杂度都不符合条件,我们只能另寻他路。
- 在n很大但ai很小且大量重复的情况下,对多个价值相同的物品逐个遍历是很亏的。假设价值为v的物品共有x个,我们观察其所能构成的所有价值,就会发现:只要能找到一种方法可以构成0~x中的任意数,就能替代这x个物品。这就是多重背包的二进制优化。假设x在二进制下最高位为第m+1位,那么我们只要有20,21,·······,2m-1,x-2m+1这logx个数字就能构成小于等于x的所有数。
- 将x个物品拆成logx个物品,仅限于同一种物品。本题中共有n个物品,拆分后最好情况下是logn,最坏情况下似乎仍然是n。但题中给出了一个条件:所有物品价值之和不超过2e5。我们知道极限数据肯定是尽量让每个物品的价值都各不相同,也就是1,2,3,······。在这种情况下物品价值之和为等差数列求和公式n*(n+1)/2,在2e5的限制下n最大只能到600左右,此时即便用O(n2)的0/1背包也可以解决。那么如果要扩大n的范围就只能缩小a的范围,打表发现物品的种类不会超过500,此时算法整体复杂度稳定在O(wlogn)。
代码实现
#include <iostream>
using namespace std;
#include <vector>
vector<int> w;
int cnt[200001],dp[200001];
int main(){
int i,j,x,mw,n;
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n>>mw;
for(i=1;i<=n;i++){
cin>>x;
cnt[x]++;
}
for(i=1;i<=2e5;i++){
x=1;
while(x<=cnt[i]){
w.emplace_back(x*i);
cnt[i]-=x;
x<<=1;
}
if(cnt[i])w.emplace_back(cnt[i]*i);
}
for(auto &i:w)
for(j=mw;j>=i;j--)
dp[j]=max(dp[j],dp[j-i]+i);
cout<<dp[mw];
return 0;
}