在有效时间内将正整数写为 2 的幂和的总方法数

Posted

技术标签:

【中文标题】在有效时间内将正整数写为 2 的幂和的总方法数【英文标题】:Total number of ways to write a positive integer as the sum of powers of 2 in efficient time 【发布时间】:2013-05-08 05:01:29 【问题描述】:

我一直在查看Number of ways to write n as a sum of powers of 2,它工作得很好,但我想知道如何提高该算法的运行时效率。它无法在任何合理的时间内(不到 10 秒)计算超过 ~1000 的任何东西。

我假设它与将其分解为子问题有关,但不知道如何去做。我在想像 O(n) 或 O(nlogn) 运行时之类的东西——我相信它是有可能的。我只是不知道如何有效地分工。

通过 Chasefornone 编码

 #include<iostream>
using namespace std;

int log2(int n)

    int ret = 0;
    while (n>>=1) 
    
        ++ret;      
    
    return ret;


int power(int x,int y)

    int ret=1,i=0;
    while(i<y)
    
        ret*=x;
        i++;
    
    return ret;


int getcount(int m,int k)

    if(m==0)return 1;
    if(k<0)return 0;
    if(k==0)return 1;
    if(m>=power(2,k))return getcount(m-power(2,k),k)+getcount(m,k-1);
    else return getcount(m,k-1);



int main()

    int m=0;
    while(cin>>m)
    
        int k=log2(m);
        cout<<getcount(m,k)<<endl;
    
    return 0;

【问题讨论】:

请向我们展示您的代码... 如果您最多可以使用 2 的每个幂一次,则答案是一个常数:ONE。否则,只求 2 的幂就足以解决问题。 编辑添加代码 - 来自 Chasefornone - 不是我自己的。 【参考方案1】:

由于我们正在处理某个基数的幂(在本例中为 2),我们可以在 O(n) 时间(和空间,如果我们考虑固定大小的计数)轻松完成。

关键是分区的生成函数。设p(n) 为将n 写成基数b 的幂的次数。

然后考虑

        ∞
f(X) =  ∑  p(n)*X^n
       n=0

可以把f写成无穷积,

        ∞
f(X) =  ∏  1/(1 - X^(b^k))
       k=0

如果只希望系数达到某个极限l,则只需考虑b^k &lt;= l 的因素。

以正确的顺序(降序)将它们相乘,在每一步都知道只有索引可被b^i 整除的系数是非零的,所以只需要将n/b^k + n/b^(k-1) + ... + n/b + n 系数相加,总共O(n)

代码(不防止较大参数溢出):

#include <stdio.h>

unsigned long long partitionCount(unsigned n);

int main(void) 
    unsigned m;
    while(scanf("%u", &m) == 1) 
        printf("%llu\n", partitionCount(m));
    
    return 0;


unsigned long long partitionCount(unsigned n) 
    if (n < 2) return 1;
    unsigned h = n /2, k = 1;
    // find largest power of two not exceeding n
    while(k <= h) k <<= 1;
    // coefficient array
    unsigned long long arr[n+1];
    arr[0] = 1;
    for(unsigned i = 1; i <= n; ++i) 
        arr[i] = 0;
    
    while(k) 
        for(unsigned i = k; i <= n; i += k) 
            arr[i] += arr[i-k];
        
        k /= 2;
    
    return arr[n];

工作速度足够快:

$ echo "1000 end" | time ./a.out
1981471878
0.00user 0.00system 0:00.00elapsed

【讨论】:

完美!谢谢你。我仍然习惯于使用二进制和有符号/无符号数字进行所有这些数学运算。您将如何防止大输入溢出?分成2块运行两次? 不,第一次溢出会发生在结果中,你需要使用任意精度类型。然后下一个可能性是索引中的溢出 (i),为此,使用 unsigned long long 会带你走得足够远,以至于在任何溢出危险之前你就会内存不足。另外,如果你想处理大于几千的输入,你最好 calloc 数组而不是把 VLA 放在堆栈上。【参考方案2】:

解决此类问题的一种普遍适用的方法是缓存中间结果,例如如下:

#include <iostream>
#include <map>

using namespace std;

map<pair<int,int>,int> cache;

/* 
The log2() and power() functions remain unchanged and so are omitted for brevity
 */
int getcount(int m,int k)

    map<pair<int,int>, int>::const_iterator it = cache.find(make_pair(m,k));
    if (it != cache.end()) 
        return it->second;
    
    int count = -1;
    if(m==0) 
       count = 1;
     else if (k<0) 
        count = 0;
     else if (k==0) 
       count = 1;
     else if(m>=power(2,k)) 
        count = getcount(m-power(2,k),k)+getcount(m,k-1);
     else 
        count = getcount(m,k-1);
    
    cache[make_pair(m,k)] = count;
    return count;


/* 
The main() function remains unchanged and so is omitted for brevity
 */

原始程序(我称之为nAsSum0)的结果是:

$ echo 1000 | time ./nAsSum0
1981471878
59.40user 0.00system 0:59.48elapsed 99%CPU (0avgtext+0avgdata 467200maxresident)k
0inputs+0outputs (1935major+0minor)pagefaults 0swaps

对于带缓存的版本:

$ echo 1000 | time ./nAsSum
1981471878
0.01user 0.01system 0:00.09elapsed 32%CPU (0avgtext+0avgdata 466176maxresident)k
0inputs+0outputs (1873major+0minor)pagefaults 0swaps

...都在 Cygwin 下的 Windows 7 PC 上运行。因此,带有缓存的版本太快了,time 无法准确测量,而原始版本大约需要 1 分钟才能运行。

【讨论】:

以上是关于在有效时间内将正整数写为 2 的幂和的总方法数的主要内容,如果未能解决你的问题,请参考以下文章

求解自然数幂和的若干种方法

实现基于整数的幂函数 pow(int, int) 的最有效方法

康复计划#3 简单常用的几种计算自然数幂和的方法

2的幂和按位与&——效率

检查数字 3^x * 5^y 的有效方法

51 NOD 1138 连续整数的和(简单数学公式)