题目
题意:判断非负整数 n (n<=1 000 000)能否表示为各不相同的非负数字的阶乘之和,比如9 = 1!+2!+3!
解题思路
由于这些非负数字可以不是相邻的,所以要枚举所有的组合,那么就可以用二进制取子集的方法。
利用二进制的“开关”特性枚举;
具体为:假设给定集合A大小为n,则想象A = {a[0], a[1], …, a[n-1]}的每个元素对应一个开关位(0或1),0表示不出现,1表示出现;当每个元素的开关位的值确定时,就得到一个子集,因此共有2^n-1种情况(全0为空集);
我们利用区间[1, 2^n-1]上的每个整数来对应每个子集,对应方法是:遍历该整数二进制表示的每一位,若该位为1则相应子集中存在对应元素,否则不存在。
在本题中,由于
遍历
需要注意的是:
- 提前打表可以节约后续的计算时间,即将所有满足题意的数先标记出来,判断时直接查询即可。
- 子集法中的0(空集)在本题属于特殊情况,因为0不能由其它数阶乘得到,但在枚举时中会被标记。
AC代码
#include <iostream>
using namespace std;
const int maxn = 1e7 << 1;
bool judge[maxn]; //打表判断i是否为阶乘之和
int fac[15]; //存放阶乘
void init()
{
//计算阶乘
fac[0] = 1;
for (int i = 1; i <= 10; ++i)
fac[i] = fac[i-1] * i;
//打表
fill(judge, judge+maxn, false);
int sum = 0;
for (int i = 0; i < (1<<10); ++i) //二进制子集法,遍历2^10-1个子集
{
sum = 0;
for (int j = 0; j < 10; ++j) //移位检测1出现的位置
if (i & (1<<j))
sum += fac[j];
judge[sum] = true;
}
judge[0] = false; //特殊:0不是阶乘之和!
}
int main()
{
int n, m;
init();
while (cin >> n && n >= 0)
{
if (judge[n]) cout << "YES" << endl;
else cout << "NO" << endl;
}
return 0;
}