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