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

题目

题意:判断非负整数 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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章