参考资料:整数划分问题
一、问题描述:
一个整数总可以拆分为2的幂的和,例如:
7=1+2+4
7=1+2+2+2
7=1+1+1+4
7=1+1+1+2+2
7=1+1+1+1+1+2
7=1+1+1+1+1+1+1
总共有六种不同的拆分方式。再比如:4可以拆分成:4 = 4,4 = 2 + 2,4=1+1+2,4 = 1 + 1 + 1 + 1。用g(n)表示n的不同的幂2拆分的种数,例如g(7)=6。求g(n),1<=n<=1000000,输出g(n)%1000000000。
二、问题分析:
该问题实质上是整数划分问题。整数划分问题是算法中的一个经典命题之一,有关这个问题的讲述在讲解到递归时基本都将涉及。经典的整数划分,是指把一个正整数n写成如下形式:n=m1+m2+...+mi。其中mi为正整数,并且1 <= mi <= n,则{m1,m2,...,mi}为n的一个划分。如果{m1,m2,...,mi}中的最大值不超过m,即max(m1,m2,...,mi)<=m,则称它属于n的一个m划分。这里我们记n的m划分的个数为f(n,m)。例如但n=4时,他有5个划分,{4},{3,1},{2,2},{2,1,1},{1,1,1,1}。注意4=1+3 和 4=3+1被认为是同一个划分。该问题是求出n的所有划分个数,即f(n, n)。求f(n,m)的方法一般有两种:递归法,母函数法。
1. 递归法:
f(n, m)=1, n=1, or m=1;
=f(n, n), n<m;
=1+f(n, m-1), n=m;
=f(n-m, m)+f(n, m-1), n>m;
2. 母函数法:
所谓母函数,即为关于x的一个多项式G(x):有 G(x)=a0 + a1*x + a2*x^2 + a3*x^3 + ...,则我们称G(x)为序列(a0, a1, a2, ...)的母函数。对于整数划分,假设n的某个划分中,1的出现个数记为a1,2的个数记为a2,..., i的个数记为ai。显然:ai<=n/i; (0<= i <=n)。因此n的划分数f(n,n),也就是从1到n这n个数字中抽取这样的组合,每个数字理论上可以无限重复出现,即个数随意,使他们的总和为n。显然,数字i可以有如下可能,出现0次(即不出现),1次,2次,..., k次,等等。把数字i用(x^i)表示,出现k次的数字i用 x^(i*k)表示, 不出现用1表示。例如数字2用x^2表示,2个2用x^4表示,3个2用x^6表示,k个2用x^2k表示。则对于从1到N的所有可能组合结果我们可以表示为:
G(x) = (1+x+x^2+x^3+...+x^n) (1+x^2+x^4+...) (1+x^3+x^6+...) ... (1+x^n)
= g(x,1) g(x,2) g(x,3) ... g(x, n)
= a0 + a1* x + a2* x^2 + ... + an* x^n + ... ; (展开式)
上面的表达式中,每一个括号内的多项式,即g(x,i) 代表了数字i的参与到整数n划分中的所有可能情况。因此该多项式展开后,由于x^a * x^b=x^(a+b),因此 展开式中ai*x^i 就代表了i的划分,展开后(x^i)项的系数ai也就是i的所有划分的个数,即f(n,n)=an。
对整数的幂2划分,设 _m=2^_exp2,2^_exp2<=n<2^(_exp2+1),则有,g(n)=f(n, _m):
1. 递归法:
f(n, m, exp2)=1, n=1, or m=1;
=f(n, _m, _exp2), n<m;
=(1+f(n, m/2, exp2-1))%1000000000, n=m;
=(f(n-m, m)+f(n, m/2, exp2-1))%1000000000, n>m;
2. 母函数法:
G(x, n) = (1+x+x^2+x^3+...+x^n) (1+x^2+x^4+...) (1+x^4+x^8+...) ... (1+x^_m)
= g(x,1) g(x,2) g(x,4) ... g(x, _m)
= a0 + a1* x + a2* x^2 + ... + an* x^n + ... ; (展开式)
三、代码实现:
#include<cstdio>
#include<cstring>
#include<cassert>
unsigned int arr[21][1000001];
unsigned int arr2[21][1000001];
int getPartitionNum(int n);
int getPNum(int n);
int main(int argc,char** argv){
int n=1,k=0;
memset(arr2,0,sizeof(arr2));
while(scanf("%d",&n)!=EOF){
assert(getPartitionNum(n)==getPNum(n));
printf("%d\n",getPNum(n));
}
return 0;
}
void getM(int n,int &m,int &exp2){
m=1,exp2=0;
while((m<<=1)<=n){
++exp2;
}
m>>=1;
}
int getPartition(int n,int m,int exp2){
if(arr2[exp2][n]!=0){
return arr2[exp2][n];
}else{
if(n==1||m==1){
return arr2[exp2][n]=1;
}
if(n<m){
getM(n,m,exp2);
return arr2[exp2][n]=getPartition(n,m,exp2);
}else if(n==m){
return arr2[exp2][n]=(1+getPartition(n,m>>1,exp2-1))%1000000000;
}else{
return arr2[exp2][n]=(getPartition(n-m,m,exp2)+getPartition(n,m>>1,exp2-1))%1000000000;
}
}
}
int getPartitionNum(int n){//记忆递归法
int m,exp2;
if(n<1||n>1000000){
return -1;
}
getM(n,m,exp2);
return getPartition(n,m,exp2);
}
int getPNum(int n){//母函数法
int m,exp2;
if(n<1||n>1000000){
return -1;
}
memset(arr,0,sizeof(arr));
getM(n,m,exp2);
for(int i=0,t=1,k;i<=exp2;++i,t*=2){
arr[i][k=0]=1;
while((k+=t)<=n){
arr[i][k]=1;
}
}
for(int i=1,t=2,k,m;i<=exp2;++i,t*=2){
m=0,k=t;
while(m+k<=n){
m=0;
while(m<=n){
arr[0][m+k]=(arr[0][m+k]+arr[0][m])%1000000000;
++m;
}
k+=t;
}
}
return arr[0][n];
}
四、测试结果:
输入:
0
输出:
-1
输入:
1
输出:
1
输入:
2
输出:
2
输入:
3
输出:
2
输入:
4
输出:
4
输入:
7
输出:
6
输入:
900
输出:
990388828
输入:
10000
输出:
573449444
输入:
100000
输出:
31071078
输入:
1000000
输出:
59487556
输入:
1000001
输出:
-1