[ARC201B] Binary Knapsack
题目传送门
贪心
题意
有 \(N\) 个物品,第 \(i\) 个物品重量为 \(2^{X_i}\),价值为 \(Y_i\)。
你可以从中选择任意多个重量之和不超过 \(W\) 的物品,求它们价值之和的最大值。
\(1\le N\le 2\times 10^5,1\le W\le 10^{18},0\le X_i<60,1\le Y_i\le 10^9\)。
题解
我的贪心太弱了,写篇题解巩固一下。
数据范围很大,不能背包,会想到考虑贪心。
这题的关键信息就是所有物品的重量都是 \(2^k\),我们考察这使得这个背包具备了什么性质。
首先物品种类最多只有 \(60\) 种,且两个重量同为 \(2^k\) 的物品可以合并为一个重量为 \(2^{k+1}\) 的物品,其新物品的价值为两个物品的价值之和,选择这个新物品的意义就是同时选择这两个物品。
我们要制定一个合并的策略,唯一准则是要满足对于任何原问题的解,存在一个合并后问题的解使得总价值相同。
策略如下:
- 从低到高位合并,假设当前合并到了第 \(i\) 位。
- 如果 \(W\) 在当前位置是 \(1\) 是必选的,因为这个操作不会影响更高位的选择。
- 进行完 2. 操作后,当前这位一定变为了 \(0\),由于后面的物品的重量都不小于当前物品重量的两倍,所以重量为 \(2^i\) 和 \(2^{i+1}\) 物品对后面的限制是一样的,所以此时我们不管选一个还是选两个当前位的物品,都会对更高位产生一样的限制,换句话说,拿一件时可以多拿一件,此时我们不妨直接贪心地将所有物品从大到小两两合并,传递到下一位去。
- 如果当前位置有奇数个物品,合并的时候就会多出来一个,但是我们此时不妨直接将这个物品的重量看做 \(2^{i+1}\) 传递到下一位去,因为对后面的限制是一样的。
到这里就讲的差不多了,上代码。
代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
int n,w;
vector<int> a[60];
void solve(){
int ans=0;
cin>>n>>w;
for(int i=1;i<=n;i++){
int x,y;
cin>>x>>y;
a[x].emplace_back(y);
}
for(int i=0;i<60;i++){
if(a[i].empty()) continue;
sort(a[i].begin(),a[i].end());
if(w&(1LL<<i)) ans+=a[i].back(),a[i].pop_back();
if(a[i].empty()||i==59) continue;
for(int j=a[i].size()-1;j>=1;j-=2) a[i+1].emplace_back(a[i][j]+a[i][j-1]);
if(a[i].size()&1) a[i+1].emplace_back(a[i][0]);
}
for(int i=0;i<60;i++) a[i].clear();
cout<<ans<<"\n";
}
signed main(){
cin.tie(nullptr)->sync_with_stdio(false);
int T; cin>>T;
while(T--) solve();
return 0;
}
本文来自博客园,作者:_AzureSky,转载请注明原文链接:https://www.cnblogs.com/-AzureSky-/p/19088093

浙公网安备 33010602011771号