[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}\) 的物品,其新物品的价值为两个物品的价值之和,选择这个新物品的意义就是同时选择这两个物品。

我们要制定一个合并的策略,唯一准则是要满足对于任何原问题的解,存在一个合并后问题的解使得总价值相同。

策略如下:

  1. 从低到高位合并,假设当前合并到了第 \(i\) 位。
  2. 如果 \(W\) 在当前位置是 \(1\) 是必选的,因为这个操作不会影响更高位的选择。
  3. 进行完 2. 操作后,当前这位一定变为了 \(0\),由于后面的物品的重量都不小于当前物品重量的两倍,所以重量为 \(2^i\)\(2^{i+1}\) 物品对后面的限制是一样的,所以此时我们不管选一个还是选两个当前位的物品,都会对更高位产生一样的限制,换句话说,拿一件时可以多拿一件,此时我们不妨直接贪心地将所有物品从大到小两两合并,传递到下一位去。
  4. 如果当前位置有奇数个物品,合并的时候就会多出来一个,但是我们此时不妨直接将这个物品的重量看做 \(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;  
}

posted @ 2025-09-12 16:18  _AzureSky  阅读(15)  评论(0)    收藏  举报