int PartitionCount(int n, int m)
{
if(n==1 || m==1)
return 1;
else if(n < m)
return PartitionCount(n,n);
else if(n==m)
return 1+PartitionCount(n,n-1);
else
return PartitionCount(n,m-1)+PartitionCount(n-m,m);
}
下面我们从另一个角度即“母函数”的角度来考虑这个问题。
所谓母函数,即为关于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,
显然: ak<=n/k; (0<= k <=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 + ... ; (展开式)
上面的表达式中,每一个括号内的多项式代表了数字i的参与到划分中的所有可能情况。因此该多项式展开后,由于x^a * x^b=x^(a+b),因此 x^i 就代表了i的划分,展开后(x^i)项的系数也就是i的所有划分的个数,即f(n,n)=an (上式中g(x,i)表示数字i的所有可能出现情况)。
由此我们找到了关于整数划分的母函数G(x);剩下的问题是,我们需要求出G(x)的展开后的所有系数。
为此我们首先要做多项式乘法,对于我们来说并不困难。我们把一个关于x的一元多项式用一个整数数组a[]表示,a[i]代表x^i的系数,即:
g(x) = a[0] + a[1]x + a[2]x^2 + ... + a[n]x^n;
具体实现源码:
多项式相乘:
/*
多项式相乘,第二个多项式有点特殊,只有x的k的倍数项。另外,其项的系数为1。
所以,程序中第二个循环中的跳跃步数为k。
*/
void PolyMul(int*& a, int n, int k)
{
int i,j;
int num = n+1;
int* c = new int[n+1]();
for (i=0;i<num;++i)
{
for(j=0;j<num-i;j+=k)
c[i+j] += a[i]; // 等价于c[i+j] += a[i]*1;
}
for (i=0;i<num;++i) // 把最终结果保存到a中,用于与下一个多项式相乘
a[i]=c[i];
}
n的整数划分个数:
// x^n的系数,即为n的整数划分的个数
int CalFactorN(int n)
{
int num = n+1; // x^0 ~ x^n , 总共n+1个。
int* a = new int[num]();
for (int i=0;i<num;++i) // 第一个多项式
a[i] = 1;
for (int k=2;k<=n;++k)
{
PolyMul(a,n,k); // 与第k个多项式相乘
}
return a[n]; // n的整数划分的个数
}
在题目给的例子中,我们可以发现两个规律:
(1)除了第一行只有一个数6之外,从上到下,在第二个加数为1时,下一个方案第一个加数必定减1,转到下一行。
(2)从左到右,右边的加数一定不大于左边的加数。在同一行中,上一个方案与下一个方案的不同在于,与加数为1相邻的左边那个不为1的加数分解出一个1得到下一方案。
基于以上思路,源码:
/*
整数的划分,输入n,输出划分方案数
*/
int IntPartition(int n)
{
int* a = new int[n]();
// n 和 n-1 1 特殊处理
a[0] = n;
cout<<a[0]<<endl;
a[0] = n-1;
a[1] = 1;
cout<<a[1]<<a[0]<<endl;
int count = 2;
//第一个加数为1时,后面的所有加数必定都为1,即为最后一个方案。
while(a[0]>1)
{
// 第一种情况,第二个加数等于1.
if(a[1]==1)
{
a[0]--;
int i = 1;
int rest = n-a[0];
while(rest>a[0])
{
a[i++] = a[0];
rest -= a[0];
}
a[i] = rest;
int j=i+1;
while(j<n)
{
if(a[j]==0)
break;
a[j++]=0;
}
// 输出当前方案
while(i) cout<<a[i--];
cout<<a[0]<<endl;
// 计数
count++;
}
// 第二种情况,第二个加数不为1,那么必定大于1
else if(a[1]>1)
{
int i = 1;
while(a[i]>1) i++;
a[i-1]--;
while(a[i]!=0) i++;
a[i] = 1;
// 输出当前方案
while(i)
cout<<a[i--];
cout<<a[0]<<endl;
// 计数
count++;
}
}
return count;
}