51nod 1383&1048 整数分解为2的幂 [递推]【数学】

66 篇文章 0 订阅
9 篇文章 0 订阅

题目连接:http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1048

————————————————————————————————————-.
整数分解为2的幂

基准时间限制:3 秒 空间限制:131072 KB 分值: 1280 难度:9级算法题

任何正整数都能分解成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

Input
输入N(1 <= N <= 10^30)

Output
划分方法的数量

Input示例

7

Output示例

6
————————————————————————————————————-.

首先能够dp

设dp[i][j]表示组成的数是 i ,方案中最大的数是2j的方案数。
为了避免重复,dp[i][j]转移时枚举下一个数必须大于等于 2j
那么可以得出: dp[i][j]=jk=0dp[i2j][k]

能够做到 O(nlog(n)) 的复杂度

通过打表找规律等 ,可以递推得到如下规律

dp[i]=dp[i2]+dp[i/2],idp[i]=dp[i1],i

这样的话复杂度就是 O(n)

然而对于51nod 1383 这个复杂度就够用了,但是对于1048的 n(1030) ,就完全不够用了,

如果n很大怎么办?
那样就要挖掘一下分解方案的性质了。

1.对于一个数n,假设它二进制下有m个1,分别是第 a1,a2am 位。对于n的任意一种分解方案,把所有2的幂升序排序,然后可以划分成m段,其中第i段的和是 2ai
2.对于一个数 2i 的一种划分方案,如果不是只有它本身一个数,一定可以把这些2的幂升序排序,然后分成两段,每一段的和都是 2i1

证明并不难。有了这些性质,就可以设新的状态了。

首先设g[i][j]表示做完了前i段(即n二进制下的前i个1位),最大的数是 2j ,方案数是多少。
再设一个辅助数组f[i][j],表示组成 2i ,最大的数是 2j 的方案数。
接下来枚举第 i1 段的最大数是多少,假设是 2k ,那么 g[i][j]=jk=0g[i1][k]f[ik][jk]
其中 ik,jk 的意义在于:要控制后面的数都大于等于 2k ,每个数都要除掉 2k ,那么就不会算重了。
f[i][j]的转移类似

时间复杂度 O(log3(n))

转(chao)自(xi)

附本题代码
———————————————————————————-.

O(n)

int dp[N];
int main(){
    int n;
    scanf("%d",&n);
    dp[0]=1;
    for(int i=1;i<=n;i++){
        if(i&1){
            dp[i]=dp[i-1];
        }
        else {
            dp[i]=dp[i-1]+dp[i/2];
        }
        if(dp[i]>MOD) dp[i]-=MOD;
    }
    printf("%d\n",dp[n]);
    return 0;
}

O(log3(n))

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int N=99,M=170,mo=1e9;

typedef long long LL;

char c[N];

int tot;

struct num
{
    int w,a[M];
}f[N+5][N+5],ans,n,p,t,s[N+5],g[N+5][N+5];

num operator + (const num &a,const num &b)
{
    t.w=max(a.w,b.w);
    //t.a[1]=0;
    memset(t.a,0,sizeof(t.a));
    for (int i=1;i<=t.w;i++)
    {
        t.a[i]+=a.a[i]+b.a[i];
        if (t.a[i]>=mo)
        {
            t.a[i+1]=1;
            t.a[i]-=mo;
        }//else t.a[i+1]=0;
    }
    if (t.a[t.w+1]>0) t.w++;
    //memset(t.a+t.w+1,0,sizeof(t.a+t.w+1));
    return t;
}

num operator * (const num &a,const num &b)
{
    memset(t.a,0,sizeof(t.a));
    for (int i=1;i<=a.w;i++)
    {
        for (int j=1;j<=b.w;j++)
        {
            t.a[i+j]+=(t.a[i+j-1]+(LL)a.a[i]*b.a[j])/mo;
            t.a[i+j-1]=(t.a[i+j-1]+(LL)a.a[i]*b.a[j])%mo;
        }
    }
    for (t.w=a.w+b.w;t.w>1 && t.a[t.w]==0;t.w--);
    return t;
}

int main()
{
    freopen("data.out","w",stdout);
    scanf("%s",c+1);
    int l=strlen(c+1);
    n.w=l/9+1;
    for (int i=1;i<=l;i++)
    {
        int j=(l-i)/9;
        n.a[j+1]=n.a[j+1]*10+c[i]-48;
    }
    f[0][0].w=f[0][0].a[1]=1;
    for (int i=1;i<=N;i++)
    {
        for (int j=0;j<i;j++)
        {
            for (int k=0;k<=j;k++)
            {
                f[i][j]=f[i][j]+f[i-1][k]*f[i-1-k][j-k];
            }
        }
        f[i][i].w=f[i][i].a[1]=1;
    }
    tot=0;
    for (int i=0;i<=N;i++)
    {
        if (n.a[1]&1)
        {
            tot++;
            if (tot==1)
            {
                for (int j=0;j<=i;j++) g[1][j]=f[i][j];
            }else
            {
                for (int j=0;j<=i;j++)
                {
                    for (int k=0;k<=j;k++)
                    {
                        g[tot][j]=g[tot][j]+g[tot-1][k]*f[i-k][j-k];
                    }
                }
            }
        }
        int r=0;
        for (int j=n.w;j;j--)
        {
            int t=n.a[j];
            n.a[j]=((LL)r*mo+t)/2;
            r=((LL)r*mo+t)%2;
        }
    }
    for (int i=0;i<=N;i++) ans=ans+g[tot][i];
    printf("%d",ans.a[ans.w]);
    for (int i=ans.w-1;i;i--)
    {
        for (int j=1e8;j;j/=10)
        {
            printf("%d",ans.a[i]/j);
            ans.a[i]%=j;
        }
    }
    printf("\n");
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值