[51Nod 1048] 整数分解为2的幂 V2

Description

任何正整数都能分解成2的幂,给定整数N,求N的此类划分方法的数量!
比如N = 7时,共有6种划分方法。

7=1+1+1+1+1+1+1
=1+1+1+1+1+2
=1+1+1+2+2
=1+2+2+2
=1+1+1+4
=1+2+4
(1 <= N <= 10^30)

Solution

O(N)的做法十分显然。
f[i]=f[i1]+f[i/2] ,当i为偶数
f[i]=f[i1] 当i为奇数。

既然N这么大了,考虑log 做法
假定 N=2p
似乎它与 2p1 有一定关系。

f[i][l][r] 表示做到 2i ,当前分解最大的数是 2r ,最小的是 2l
这样很好的避免了重复问题。
显然 f[i][l][r]=k=lrf[i1][l][k]f[i1][k][r]

log4N 似乎有点大。
事实上, f[i][l][r]=f[i1][l1][r1] (即分解的所有数同时除以2)
那么只需要保存 f[i][0][r] 就足够。
DP就变为2维的了。
复杂度 log3N

N不是 2l 怎么办。

完全可以将N二进制分解,在有1的位置做DP,方法同上面相同,因为两个二进制位间相互独立,互不影响。相当于将前面所有二进制位合并后,与当前位合并,一直合并下去。

复杂度同样 log3N
高精度注意常数优化

Code

#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cmath>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fod(i,a,b) for(int i=a;i>=b;i--)
#define N 105
#define cl(a) memset(a,0,sizeof(a));
#define lq 1000000000
using namespace std;
typedef long long LL;
int m,p[N];
LL cf[11]; 
struct node
{
    int a[180];
}f[N][N],g[N][N],n;
node operator *(node x,node y)
{
    node z;
    cl(z.a);
    z.a[0]=x.a[0]+y.a[0]-1;
    if((!x.a[1]&&x.a[0]<2)||(!y.a[1]&&y.a[0]<2))
    {
        z.a[0]=1,z.a[1]=0;
        return z;
    }
    fo(i,1,x.a[0])
    {
        fo(j,1,y.a[0])
        {
            LL v=(LL)x.a[i]*(LL)y.a[j]+(LL)z.a[i+j-1];
            z.a[i+j]+=(int)(v/lq);
            z.a[i+j-1]=(int)(v%lq);
        }
    }
    if(z.a[z.a[0]+1]) z.a[0]++;
    return z;
}   
node operator +(node x,node y)
{
    node z;
    cl(z.a);
    z.a[0]=max(x.a[0],y.a[0]);
    fo(i,1,z.a[0])
    {
        z.a[i+1]+=(z.a[i]+=x.a[i]+y.a[i])/lq;
        z.a[i]%=lq;
    }
    z.a[0]+=(z.a[z.a[0]+1]);
    return z;
}
node operator /(node x,LL y)
{
    node z;
    memset(z.a,0,sizeof(z.a));
    z.a[0]=x.a[0];
    fod(i,z.a[0],1)
    {
        if(i!=1) z.a[i-1]+=(z.a[i]+x.a[i])%y*lq;
        z.a[i]=(z.a[i]+x.a[i])/y;
    }
    z.a[0]-=(!z.a[z.a[0]]);
    return z;
}
int get()
{
    node n1=n;
    int i=0;
    while(n1.a[0]) 
    {
        if(n1.a[1]%2) p[++p[0]]=i;
        n1=n1/2,i++;
    }
    return i;
}
int main()
{
    char st[2000];
    scanf("%s",st+1);
    cf[0]=1;
    fo(i,1,10) cf[i]=cf[i-1]*10;
    int l1=strlen(st+1);
    fo(i,1,l1) n.a[(i-1)/9+1]+=(st[l1-i+1]-'0')*cf[i-(i-1)/9*9-1];
    n.a[0]=(l1-1)/9+1;
    int l=get();
    f[0][0].a[0]=f[0][0].a[1]=1;
    int v=0;
    fo(i,1,l)
    {
        f[i][i].a[0]=f[i][i].a[1]=1;
        fo(j,0,i-1)
        {
            f[i][j].a[0]=1;
            f[i][j].a[1]=0;
            fo(k,0,j) f[i][j]=f[i][j]+f[i-1][k]*f[i-1-k][j-k];
        }
    }
    fo(i,0,p[1]) g[1][i]=f[p[1]][i];  
    fo(i,2,p[0])
    {
        fo(j,0,p[i])
        {
            fo(k,0,j) g[i][j]=g[i][j]+g[i-1][k]*f[p[i]-k][j-k];
        }
    }
    node ans;
    cl(ans.a);
    fo(i,0,p[p[0]]) ans=ans+g[p[0]][i];
    fod(i,ans.a[0],1)
    {
        if(i!=ans.a[0]) fod(j,8,1) if(ans.a[i]<cf[j]) printf("0");  
        printf("%d",ans.a[i]);
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值