二进制枚举算法
有的题目需要我们把一个集合的所有子集都枚举出来,一般来说是有点小难,但是因为我们用的二进制表示的集合,那么也就有用二进制的操作简化运算的方法啦, 所谓二进制枚举 就是一种暴力的方式,用0,1来代表一个数字存在或不存在。 例如 0101 可以理解为 第一个物品要了,第二个不要,第三个要了,第四个不要。通过这种方式可以在数据量较小的情况下将列举子集的操作变换为位操作。
二进制手表
题目链接: Leetcode 二进制手表
二进制手表顶部有 4 个 LED 代表 小时(0-11),底部的 6 个 LED 代表 分钟(0-59)。每个 LED 代表一个 0 或 1,最低位在右侧。例如,下面的二进制手表读取 “3:25” 。
给你一个整数 turnedOn ,表示当前亮着的 LED 的数量,返回二进制手表可以表示的所有可能时间。你可以 按任意顺序 返回答案。
小时不会以零开头:
-
例如,“01:00” 是无效的时间,正确的写法应该是 “1:00” 。
分钟必须由两位数组成,可能会以零开头: -
例如,“10:2” 是无效的时间,正确的写法应该是 “10:02” 。
样例
示例1:
输入:turnedOn = 1
输出:["0:01","0:02","0:04","0:08","0:16","0:32","1:00","2:00","4:00","8:00"]
示例2:
输入:turnedOn = 9
输出:[]
提示:
0 < = t u r n e d O n < = 10 0 <= turnedOn <= 10 0<=turnedOn<=10
算法1 (二进制枚举)
思路分析
从提示中知道数据范围很小, 所有的组合情况只有 2 10 2^{10} 210 = 1024, 所以对于一个turnedOn直接枚举这1024种子集中符合亮起的灯数量为 turnedOn, 且其转化后的格式保证 小时部分 < 12, 分钟部分小于 < 60
C++ 代码
class Solution
{
public:
vector<string> readBinaryWatch(int turnedOn)
{
vector<string> res;
char cstr[10];
for(int i = 0; i < 1 << 10; ++i)
{
int cnt = 0;
for(int j = 0; j < 10; ++j)
if(i >> j & 1) ++cnt;
if(cnt == turnedOn)
{
// i >> 6 左移6位得到高四位表示的小时, &63 得到低6位, 即分钟的表示
int a = i >> 6, b = i & 63;
sprintf(cstr, "%d:%02d", a, b); // 方便格式转换使用sprintf
if(a < 12 && b < 60) res.push_back(cstr); // 判断是否合法.
}
}
return res;
}
};
时间复杂度 O ( 1 ) O(1) O(1)
阶乘的和
题目链接: ACwing 3481. 阶乘的和
给定一个非负整数 n,请你判断是否存在一些整数 xi, 能够使得 n = ∑ 1 ≤ i ≤ t x i ! n = \sum_{1≤i≤t}x_i! n=∑1≤i≤txi!, 其中 t ≥1, x i x_i xi≥0, x i = x j x_i=x_j xi=xj iff i = j i=j i=j。
iff 表示当且仅当。
题目大意: 即给定一个数n, 问是否能写成其他数阶乘和的形式,由于是累加符号, 这里每一个数的阶乘只能选一次,不能重复选择.
输入格式
输入包含多组测试数据。
每组数据占一行,包含一个非负整数 n。
最后一行是一个负数,表示输入结束,无需处理。
输出格式
每组数据输出一行结果,如果 n 能表示为若干数的阶乘之和,则输出 YES,否则输出 NO
样例
输入
9
-1
输出
YES
数据范围
0
≤
n
≤
1
0
6
0 ≤ n ≤ 10^6
0≤n≤106,
每组输入最多包含 100 组数据。
算法1
(01背包解法) 时间复杂度 O ( n ) O(n) O(n)
思路分析
这里的话由于输入进行查询的数最大也就 1 0 6 10^6 106, 而9! = 362800, 10! = 3628800, 故我们最多也只会用到9!, 故我们只用分析在0 ~ 9 的阶乘中选,然后凑出n的方案是否存在即可. 这不就是经典的01背包问题的思路吗,相当于物品是0 ~ 9的阶乘这几个数, 而背包的容量输入的n,不过由于是多组查询,我们不能每次都输入一个数然后做一次背包问题, 我们需要预处理一下将所有的情况都做完一遍, 即得到从0 ~ 9的阶乘中选, 然后凑出1 ~ 1 0 6 10^6 106方案的集合. 然后直接查询是否存在即可.
C++ 代码
#include<iostream>
#include<cstring>
using namespace std;
constexpr int N = 1e+6 + 10, M = 20;
int fact[M];
bool f[N];
auto main() -> int
{
ios::sync_with_stdio(false);
fact[1] = fact[2] = 1;
for(int i = 3; i <= 10; ++i) fact[i] = fact[i - 1] * (i - 1);
// 背包问题预处理所有的数的情况
f[0] = true;
for(int i = 1; i <= 10; ++i)
for(int j = 1e+6; j >= fact[i]; --j)
f[j] |= f[j - fact[i]];
int a = -1;
while(cin >> a, a >= 0)
{
if(a && f[a]) cout << "YES" << endl;
else cout << "NO" << endl;
}
return 0;
}
算法2
(二进制枚举) 时间复杂度 O ( 1 ) O(1) O(1)
思路分析
这里由前面分析可以得到最多只会用到9!, 故0 ~ 9只用到这10个数的阶乘,一共10个选择,故我们可以将这10个数的组合的和给枚举出来,而我们这里的枚举方式就是用到二进制枚举,故只需枚举一共 2 10 − 1 2^{10} - 1 210−1 = 1023种情况,所以枚举的数量很小,时间复杂度是 O ( 1 ) O(1) O(1)其中数的每一位为1,表示的是用到第几个的阶乘.
C++ 代码
#include<iostream>
#include<unordered_set>
#include<cstring>
using namespace std;
constexpr int N = 1e+6 + 10, M = 20;
int fact[M];
bool f[N];
unordered_set<int> S;
auto main() -> int
{
ios::sync_with_stdio(false);
fact[0] = fact[1] = 1;
for(int i = 2; i <= 9; ++i) fact[i] = fact[i - 1] * i;
for(int i = 1; i < 1 << 10; ++i)
{
int s = 0;
for(int j = 0; j < 10; ++j)
if(i >> j & 1) s += fact[j];
S.insert(s);
}
int a = -1;
while(cin >> a, a >= 0)
{
if(S.count(a)) cout << "YES" << endl;
else cout << "NO" << endl;
}
return 0;
}