【题目来源】
https://www.acwing.com/problem/content/3420/
【题目描述】
你有一架天平和 N 个砝码,这 N 个砝码重量依次是 W1,W2,⋅⋅⋅,WN。
请你计算一共可以称出多少种不同的正整数重量?
注意砝码可以放在天平两边。
【输入格式】
输入的第一行包含一个整数 N。
第二行包含 N 个整数:W1,W2,W3,⋅⋅⋅,WN。
【输出格式】
输出一个整数代表答案。
【数据范围】
对于 50% 的评测用例,1≤N≤15。
对于所有评测用例,1≤N≤100,N 个砝码总重不超过 10^5。
【输入样例】
3
1 4 6
【输出样例】
10
【算法分析】
● 给定的砝码样例,能称出 10 种重量,分别是:1、2、3、4、5、6、7、9、10、11。
1 = 1;
2 = 6 − 4 (天平一边放 6,另一边放 4);
3 = 4 − 1;
4 = 4;
5 = 6 − 1;
6 = 6;
7 = 1 + 6;
9 = 4 + 6 − 1;
10 = 4 + 6;
11 = 1 + 4 + 6。
● C++ 的 STL bitset 的一个重要应用场景是优化动态规划算法。bitset的位操作(移位、或运算)比传统循环更高效。相比传统数组或哈希表,位运算节省空间且支持并行操作。
详见:https://blog.csdn.net/hnjzsyjyj/article/details/148285903
● 在动态规划问题中,s|=s<<w[i] 及 s|=s>>w[i] 是高效的位运算技巧,用于实现状态转移和集合合并。以下介绍其核心原理:
(1)二进制状态表示
集合编码:用二进制位表示可达状态集合。例如 101001 对应 {0,3,5}。
(2)左移/右移操作的意义
加法扩展(s<<w[i]):左移相当于对集合中所有元素加 w[i]。例如 101001<<2 生成 10100100,对应 {2,5,7}。即 {2,5,7} 是将 101001 对应的 {0,3,5} 中每个元素加 2 后所得 。
减法扩展(s>>w[i]):右移相当于对集合中所有元素减 w[i],需确保结果非负。例如 10100100>>2 生成 00101001,对应 {0,3,5}。即 {0,3,5} 是将 10100100 对应的 {2,5,7} 中每个元素减 2 后所得 。注意:负数去掉。
(3)或运算的并集功能
集合合并(|=):通过按位或运算合并新旧集合。例如 10100100 | 00101001=10101101,对应 {0,2,3,5,7}。即将 10100100 对应的 {2,5,7} 与 00101001 对应的 {0,3,5} 取并集。
【算法代码】
#include <bits/stdc++.h>
using namespace std;
const int maxn=1e2+5;
const int maxm=1e5+5;
int g[maxn];
bitset<maxm> s;
int n;
int main() {
cin>>n;
for(int i=0; i<n; i++) cin>>g[i];
s[0]=1;
for(int i=0; i<n; i++) s|=s<<g[i];
for(int i=0; i<n; i++) s|=s>>g[i];
cout<<s.count()-1<<endl;
return 0;
}
/*
in:
3
1 4 6
out:
10
*/
【代码解析】
(1)输入处理
读取砝码数量 n 和每个砝码的重量 w[i]。
(2)bitset 初始化
s[0]=1 表示重量 0 是可以称出的初始状态。
bitset 的每一位代表一个可能的重量值。
(3)核心计算
第一个循环 s|=s<<w[i] 计算将砝码放在天平一侧能称出的重量。
第二个循环 s|=s>>w[i] 计算将砝码放在天平另一侧能称出的重量。
通过位运算高效地更新所有可能的重量组合。
(4)输出结果
s.count()-1 计算所有能称出的非零重量数量。
【参考文献】
https://www.acwing.com/solution/content/45929/
https://www.acwing.com/problem/content/3420/
https://blog.csdn.net/hnjzsyjyj/article/details/148285903
https://www.acwing.com/solution/content/160343/