前言
- 利用位运算实现的集合枚举,枚举所有可能
- n只有20, int的32位足够表示
- 砝码放一侧,直接枚举所有状态下每位1的个数
- 砝码放两侧,放一侧时的状态相减
举个栗子,n=3:1<<n = 8,则i为0~7,即:
i:000 001 010 011 100 101 110 111(1代表该砝码被用)
j为0 1 2,1<<j为 1 2 4,即:
1<<j:001 010 100(各只有一个1)
循环相与后可得当前i有哪些砝码被用了,加上其重量
更新:但是这样是错的!!
- 没有考虑多个砝码放在物品一侧的状态
#include <iostream>
#define ll long long
using namespace std;
int main() {
ios::sync_with_stdio(false);
int T, n, m;
cin >> T;
while (T--) {
cin >> n;
int a[20];
int vis[2010] = {0};
for (int i = 0; i < n; i++) cin >> a[i];
for (int i = 0; i < (1<<n); i++) { // 砝码放一侧, 枚举所有子集
int sum = 0; // 砝码重量
for (int j = 0; j < n; j++)
if (i & (1 << j)) // i: 所有子集之一, 1<<j: 只有一位有1, 则相与后可得是哪位的砝码被用
sum += a[j]; // 加上被用砝码的重量
vis[sum] = 1; // 这个重量对应1: 可称
for (int j = 0; j < n; j++) // 砝码放两侧, 放一侧时的状态相减
if (sum - a[j] >= 0) // 对每个一侧可达的重量减去一个砝码的重量(可能减去同一侧的砝码, 但只是重复赋1而已)
vis[sum - a[j]] = 1;
}
cin >> m;
for (int i = 0; i < m; i++) {
int tmp;
cin >> tmp;
if (vis[tmp]) cout << "YES" << endl;
else cout << "NO" << endl;
}
}
}
更新:新的代码
- 砝码一共三种状态,-1(放左边),0(不放),1(放右边)
- 两种状态:用20位表示 三种状态:用40位表示 (2^40超时)
- 一共20个砝码,最重的情况为2000
- 依次枚举第i个砝码在重量w情况下可能出现的情况 比如物品和砝码同侧(w-i),不同侧(w+i),不放(w)
#include <iostream>
using namespace std;
int main() {
ios::sync_with_stdio(false);
int T, n, m;
cin >> T;
while (T--) {
cin >> n;
int a[101] = {0};
for (int i = 0; i < n; i++) cin >> a[i];
int vis1[2010] = {0}; // 保存了哪些重量可以被称重
int vis2[2010] = {0}; // 临时数组, 保存某个状态下通过某个砝码能更新的重量
vis1[0] = 1;
for (int i = 0; i < n; i++) { // 枚举每个砝码
for (int j = 0; j < 2000; j++) {
if (vis1[j] != 0) { // 枚举所有当前已存在的状态
vis2[j] = 1;
vis2[j+a[i]] = 1;
vis2[abs(j-a[i])] = 1;
}
}
for (int j = 0; j < 2000; j++) {
vis1[j] = vis2[j];
vis2[j] = 0;
}
}
cin >> m;
for (int i = 0; i < m; i++) {
int tmp;
cin >> tmp;
if (vis1[tmp]) cout << "YES" << endl;
else cout << "NO" << endl;
}
}
}