1. 程式人生 > >[51nod1383&1048]整數分解為2的冪

[51nod1383&1048]整數分解為2的冪

題目大意

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

最簡單的方法

首先很容易想到用DP做。
設f[i][j]表示組成的數是i,方案中最大的數是2j的方案數。
為了避免重複,f[i][j]轉移時列舉下一個數必須大於等於2j
那麼可以得出:f[i][j]=jk=0f[i2j][k]
注意到第一維是和k無關的,配合字首和優化即可做到O(nlogn)

相對巧妙的做法

狀態太大了,可不可以減去一維?
設f[i]表示組成i的方案數。如果還是列舉下一個數來轉移,那麼時間複雜度沒變。
那麼考慮換一種轉移方式。先給出方程:
f[i]=f[i1]+f[i/2][i]
f[i-1]轉移到f[i]表示加入了一個1,f[i/2]則表示f[i/2]的分解方案中,全部數乘2。這樣轉移也保證了加進來數的順序是從大到小的,故不會重複。

時間複雜度O(n)

跑得很快的做法

如果n很大怎麼辦?
那樣就要挖掘一下分解方案的性質了。

  1. 對於一個數n,假設它二進位制下有m個1,分別是第a1,a2…am位。對於n的任意一種分解方案,把所有2的冪升序排序,然後可以劃分成m段,其中第i段的和是2
    ai
  2. 對於一個數2i的一種劃分方案,如果不是隻有它本身一個數,一定可以把這些2的冪升序排序,然後分成兩段,每一段的和都是2i1

證明並不難。有了這些性質,就可以設新的狀態了。

首先設g[i][j]表示做完了前i段(即n二進位制下的前i個1位),最大的數是2j,方案數是多少。
再設一個輔助陣列f[i][j],表示組成2i,最大的數是2j的方案數。
接下來列舉第i-1段的最大數是多少,假設是2k,那麼g[i][j]=jk=0g[i1][k]f[ik][jk]
其中i-k,j-k的意義在於:要控制後面的數都大於等於2k,每個數都要除掉2k,那麼就不會算重了。
f[i][j]的轉移類似

時間複雜度O(log3n)

#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;
}

相關推薦

[51nod1383&1048]整數分解2

題目大意 任何正整數都能分解成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

[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

51Nod 1383&1048 整數分解2

題目 任何正整數都能分解成2的冪,給定整數N,求N的此類劃分方法的數量。 V1:n≤106n≤106。要對答案模1e9+71e9+7。 V2:n≤1030n≤1030。將整個答案輸出。 解題

51nod 1383 整數分解2(數列,也可以自己根據觀察找規律推理得到遞推公式)

描述: 組合數學生成函式 1383 整數分解為2的冪 1 秒   131,072 KB   80 分   5 級題 任何正整數都能分解成2的冪,給定整數N,求N的此類劃分方法的數量!由於方案數量較大,輸出

51Nod-1383-整數分解2

ACM模版 描述 題解 看到這裡,我們應該可以想到,這是一個數論問題,應該是一個什麼數列,暴力解出來小資料後,在 OEIS 中查看了一下下,發現的確是一個十分有趣的數列——Binary partition function: number of p

【51NOD 1383】整數分解2

DH ---------以上初三THU/PKU大爺---- Alan_cty LYD XHM HZJ ZZ ---以下是大神%-- YMW Samjia2000 werkeytom_ftd Crazy_czy WorldWide_D Yxuan

51nod 1383 整數分解2(遞推)

Description 任何正整數都能分解成2的冪,給定整數N,求N的此類劃分方法的數量!由於方案數量較大,輸出Mod 1000000007的結果。 比如N = 7時,共有6種劃分方法。 7=1+1+1+1+1+1+1 =1+1+1+1+1+2

51nod 1383 整數分解2

#include<bits/stdc++.h> using namespace std; const int MAXN=1000100; const int mod=1e9+7; int

[51Nod 1383] 整數分解2

Description 任何正整數都能分解成2的冪,給定整數N,求N的此類劃分方法的數量!由於方案數量較大,輸出Mod 1000000007的結果。 比如N = 7時,共有6種劃分方法。

51nod-1383 整數分解2

原題連結  收藏  關注 任何正整數都能分解成2的冪,給定整數N,求N的此類劃分方法的數量!由於方案數量較大,輸出Mod 1000000007的結果。 比如N = 7時,共有6

[51nod1138]正整數分解幾個連續自然數之和

sqrt esp 連續 奇數 mes 判斷 -i 兩個 註意 解題關鍵:註意為什麽上界是$\sqrt {2n} $ 因為函數是關於m的遞減函數,而結果必須為正整數 $a = \frac{{2n + m - {m^2}}}{{2m}} = \frac{n}{m} + \f

PTA7-37 整數分解若干項之和(20 分)超級詳解

將一個正整數N分解成幾個正整數相加,可以有多種分解方法,例如7=6+1,7=5+2,7=5+1+1,…。程式設計求出正整數N的所有整數分解式子。輸入格式:每個輸入包含一個測試用例,即正整數N (0<N≤30)。輸出格式:按遞增順序輸出N的所有整數分解式子。遞增順序是指:

一個正整數分解幾個連續的正整數之和

題目: 給定你一個數字 如:15 15可分解為 7+8 4+5+6 1+2+3+4+5 再如: 8 8不可分解為任何連續的正整數之和 所以輸出NONE 此題就是給定一個數字如果這個數字可以分解為

7-37 整數分解若干項之和(20 分)

題目連結(組合版):點選開啟連結題目大意:略。解題思路:此方法僅限於輸出組合情況,計數的話會TLE。附加題目(計數版):點選開啟連結AC 程式碼(組合版)#include<bits/stdc++.

PTA 7-12 整數分解若干項之和(20 分)

將一個正整數N分解成幾個正整數相加,可以有多種分解方法,例如7=6+1,7=5+2,7=5+1+1,…。程式設計求出正整數N的所有整數分解式子。 輸入格式: 每個輸入包含一個測試用例,即正整數N (0 < N ≤ 30)。 輸出格式: 按遞增順序輸出N的所有整數分解式

整數分解n個連續正整數

思路:等差數列求和: sn=a1*n+n*(n-1)*d/2 在這裡d為1 #include<stdio.h> //#define N 1000 #define M 10 void print(int k) {int sn=0,t=0;for(int a1=1;

7-1 整數分解若干項之和(20 分)(dfs)

思路:不帶標記的dfs,只要沒有超過和就不斷dfs直到超過了之後向前回溯。 #include<stdio.h> #include<stdlib.h> #include<string.h> #include<math

(PTA)7-1 整數分解若干項之和

題目 將一個正整數N分解成幾個正整數相加,可以有多種分解方法,例如7=6+1,7=5+2,7=5+1+1,…。程式設計求出正整數N的所有整數分解式子。 輸入格式: 每個輸入包含一個測試用例,即正整數N (0 輸出格式: 按遞增順

pta 5-37 整數分解若干項之和 (遞迴)

5-37 整數分解為若干項之和   (20分) 將一個正整數N分解成幾個正整數相加,可以有多種分解方法,例如7=6+1,7=5+2,7=5+1+1,…。程式設計求出正整數N的所有整數分解式子。 輸入格式: 每個輸入包含一個測試用例,即正整數N (0<<

面試總結:任意一個整數分解幾個連續正整數之和

       前陣子參加了國內某一大公司的面試。到了之後,人家不問出身,不問來歷,就直接開機讓我上機程式設計。因為是第一次在面試時上機操作,儘管題目不是很難,但是由於沒搞清楚機考和筆試的區別,導致最後面試失敗。現在總結一下自己在機考時碰到的一些問題,以免自己以後再犯同樣的錯