题目连接:https://www.luogu.com.cn/problem/P1441
题目的大致意思是从n个砝码中去掉m个砝码,等价于从n个砝码中取n-m个砝码,并且砝码只能放在同一边,求从n个砝码中取出n-m个砝码最多能称出多少种重量。
题目给的n的范围是20,因此我们可以用位运算来进行模拟,例如0...0(18个0)11表示只取了第0、1个砝码进行称重。因此,这里涉及到一个【在给定的二进制位中,只有特定个数的1】的问题,我们可以用【Gosper's Hack算法】来解决,具体可参考:https://zhuanlan.zhihu.com/p/360512296
这部分对应的代码段如下:
int cur = (1 << (n - m)) - 1;
int end = cur << m;
while(cur <= end){
//...称重的处理逻辑
int lb = cur & (-cur);
int l = lb + cur;
int r = (l ^ cur) / lb >> 2;
cur = l | r;
}
再考虑称重的问题,由于任意被挑选出的n-m个砝码都可以随意组合,因此我们仍然可以采用位运算的【<< + |】来实现,具体代码如下:
int mx = 0;
while(cur <= end){
bitSet<2010> S; // ai的范围是100,n的范围是20
// 什么都不放的情况,但是最后要把0减去,题目剔除了这种情况
// 但如果不设置S[0] = 1,就没有起始状态来进行转移
S[0] = 1;
// 在这里【<< + |】的效果就是包含了不取这个砝码的重量,以及所有情况加上这个砝码的重量的情形
for (int i = 0; i < n; i++) if ((cur >> i) & 1) S |= S << a[i];
// S.count() : 求1的个数
// -1 : 剔除0
mx = max(mx, (int)S.count() - 1);
//...gosper's hack
}
完整代码如下:
#include <bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
vector<int> a;
int n, m;
int mx = 0;
void solve() {
cin >> n >> m;
// n个二进制位中有n - m个1
a.resize(n);
for (int i = 0; i < n; i++) cin >> a[i];
// 00...0011...1(n-m)个1
// 11...1100...0(n-m)个1
int cur = (1 << (n - m)) - 1;
int end = cur << m;
// n位二进制中有(n - m)位1
while (cur <= end) {
// 用位信息存储该重量是否能被称上 << + | 加或不加都包含在内了
bitset<2010> S;
S[0] = 1;
for (int i = 0; i < n; i++) if ((cur >> i) & 1) S |= S << a[i];
// S.count():求S中1的个数
mx = max(mx, (int)S.count() - 1);
int lb = cur & (-cur);
int l = lb + cur;
int r = (l ^ cur) / lb >> 2;
cur = l | r;
}
cout << mx;
return;
}
signed main() {
cin.tie(nullptr)->sync_with_stdio(false);
int T;
T = 1;
//cin >> T;
while (T--) {
solve();
}
return 0;
}