整数划分

参考资料:整数划分问题

一、问题描述:

一个整数总可以拆分为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




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值