所谓整数划分,是指把一个正整数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)的方法;
下面对n和m进行考虑
<1> if (n == 1 ) f(n,m) = 1;
<2> if(m == 1) f(n,m) = 1;
<3> if(n == m) 考虑划分中是否有n:a、如果包含n,那么f(n,m) = 1;b、如果不包含n,那么划分中最大的数一定比n小,所以f(n,m) = f(n,n-1)
所以f(n,n) = f(n,n-1) + 1
<4>if(n < m) 由于划分中最大的整数肯定是小于等于n 所以这种情况f(n,m) = f(n,n)
<5>if(n > m) 考虑划分中是否有m:a、如果包含m,即{m, {x1,x2,...xi}}, 其中{x1,x2,... xi} 的和为n-m,可能再次出现m,因此是(n-m)的m划分,那么f(n,m) = f(n-m,m)
b、如果不包含m,即最大划分数为m-1,那么f(n,m) = f(n,m-1)
所以f(n,m) = f(n,m-1) + f(n-m,m)
综上可知:
int func(int n,int m)
{
if(n == 1 || m == 1) return 1;
else if(n < m ) return func(n,n);
else if(n == m) return func(n,n-1) + 1;
else return func(n-m,m) + func(n,m-1);
}
所以将整数n的所有划分个数为func(n,n)
POJ 1664 苹果放盘问题
把M个同样的苹果放在N个同样的盘子里,允许有的盘子空着不放,问共有多少种不同的分法?(用K表示)5,1,1和1,5,1 是同一种分法。
(1):当盘子数为1的时候,只有一种放法就是把所有苹果放到一个盘子里。
(2):当苹果数为1的时候,也只有一种放法,注意题目中说明,盘子之间并无顺序,所以不管这个苹果放在哪个盘子里,结果都算一个。
(3):当m<n时,因为此时最多只能放到m个盘子中去(一个里放一个),实际上就相当于把m个苹果放到m个盘子里一样,也就是f(m,m);
(4):当m==n时,此时分两种情况讨论,一种是一个盘子里放一个,只是一种,第二种是,至少有一个盘子里不放苹果这就相当于是f(m,m-1);
(5):当m>n时,也分两种情况讨论,一种是至少有一个盘子里不放苹果,这样子就相当于f(m,n-1),第二种是,先取出n个苹果一个盘子里放一个,再将剩下的m-n个苹果放到n个盘子里去,即f(m-n,n);
可以发现就应该是func(m,n);
POJ 3014 Cake Pieces and Plates
这题题意其实和POJ1664是一样的,但是数据比较大,如果直接用递归的话铁定超时,这里有两种方法,第一种是记忆话递归,就是把递归过程中已经求出的值全部保存下来,下次再递归求这个值的时候直接返回就行了;第二种方法是迭代,也就是用DP
接下来给出DP的代码
int solve()
{
for(int i = 1 ; i <= n ; i ++) dp[0][i] = 1;
for(int i = 1 ; i <= m ; i ++)
{
dp[i][0]=0;
for(int j = 1 ; j <= n ; j ++)
if(j > i) dp[i][j] = dp[i][j-1];
else
dp[i][j] = (dp[i-j][j] + dp[i][j-1]) % 1000000007;
}
return dp[m][n];
}
可以发现记忆化递归和DP的时间和空间负责度都是一样的。
思考题:1、如果盘子不为空怎么写?
2、给定N需要求出具体的每一种划分?
比如N = 4
4 = 1 + 1 + 1 + 1
4 = 2 + 1 + 1
4 = 3 + 1
4 = 2 + 2
4 = 4