EOJ 1082 Easy to AC(枚举+二进制子集法)

来源:互联网 发布:淘宝宝贝囤货怎么设置 编辑:程序博客网 时间:2024/06/08 02:20

题目

题意:判断非负整数 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则相应子集中存在对应元素,否则不存在。

在本题中,由于n<=107,而10! = 3628800,所以最多只需要考虑10以内的阶乘。

遍历[1,2101]中的每一个整数,对每个整数,检测该整数的每个二进制位,若该位为1则表示取其所在位置的阶乘累加。比如,1011表示sum=fac[0]+fac[1]+fac[3]=8.

需要注意的是:

  • 提前打表可以节约后续的计算时间,即将所有满足题意的数先标记出来,判断时直接查询即可。
  • 子集法中的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;}
原创粉丝点击