整数划分问题
给两个整数 (n) 和(k)。
将 n 划分成 k 个正整数之和的划分数
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)当(n=1)时,不论(m)的值为多少((m > 0) ),只有一种划分即 { 1 };
(2) 当 (m = 1) 时,不论(n)的值为多少,只有一种划分即 (n) 个 1,{ 1, 1, 1, ..., 1 };
(3) 当 (n = m) 时,根据划分中是否包含 (n),可以分为两种情况:
(a). 划分中包含(n)的情况,只有一个即 { n };
(b). 划分中不包含(n)的情况,这时划分中最大的数字也一定比 (n) 小,即 (n) 的所有 ( (n - 1) ) 划分。
因此 (f(n, n) = 1 + f(n, n-1));
(4) 当 (n < m) 时,由于划分中不可能出现负数,因此就相当于(f(n, n));
(5) 但 (n > m) 时,根据划分中是否包含最大值 (m),可以分为两种情况:
(a). 划分中包含 (m)的情况,即 { m, { x1, x2, ..., xi } }, 其中 { x1, x2, ..., xi } 的和为 (n - m),可能再次出现 (m),因此是((n - m))的 (m) 划分,因此这种划分
个数为 (f(n-m, m));
(b). 划分中不包含 (m) 的情况,则划分中所有值都比 $m $小,即 (n) 的 ((m - 1) ) 划分,个数为 (f(n, m - 1));
因此 (f(n, m) = f(n - m, m) + f(n, m - 1));
综合以上情况,我们可以看出,上面的结论具有递归定义特征,其中(1)和(2)属于递归边界,(3)和(4)属于特殊情况,将会转换为情况(5)。而情况(5)为通用情况,其本质主要是通过减小(m)以达到递归边界,从而解决问题。其递推表达式如下:
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++) {
if(i==j) dp[i][j]=dp[i][j-1]+1;
else if(i<j) dp[i][j]=dp[i][i];
else dp[i][j]=dp[i][j-1]+dp[i-j][j];
}
将 n 划分成最大数不超过 k 的划分数
跟1的情况是一样的,最后输出(dp[n][k])即可
将 n 划分成若干奇正整数之和的划分数
这个呢,我们首先需要调整边界状态:当(m=1)时,(f(n,m)=1);当(n=1)而(m>1)时,(f(n,m)=0)
其次,我们需要调整状态转换公式:
(f(n-m,m)+f(n,m-1); (n>m)) 应该更改为:(f(n-m,m)+f(n,m-2); (n>m))
当(n=m)时,考虑是否取(m)值,分为两种情况:
取(m)值,那么就只有一个(n)
不取(m)值,那么转化为(f(n,m-2))
所以$f(n,m)=1+f(n,m-2) $
memset(dp,0,sizeof(dp));
for(int i=0;i<=n;i++){
dp[i][1]=1;
if(i&1)//判断i是否是奇数,这是一个位运算
dp[0][i]=1;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(j&1){
if(j<=i){
dp[i][j]=dp[i-j][j]+dp[i][j-1];
}
else{
dp[i][j]=dp[i][i];
}
}
else{
dp[i][j]=dp[i][j-1];
}
}
} //
将 n 划分成若干不同整数之和的划分数
只需要考虑当(n>m)时,考虑是否取(m)
取(m)值,即{m, {x1,x2,...xi}}, 其中{x1,x2,... xi} 的和为(n-m),可能再次出现(m),因此是((n-m))的(m)划分,因此这种划分个数为(f(n-m, m-1)); 因为m不能再取了
不取(m)值, 划分中不包含(m)的情况,则划分中所有值都比(m)小,即(n)的((m-1))划分,个数为(f(n,m-1));
所以(n>m)的情况,(f(n,m)=f(n-m,m)+f(n,m-1))
终止条件 :
当(n=1)时 (m>=1) 肯定是1
但是当(n=1 ,m<1)或者 (n>1 m=1)时显然是0
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++) {
if(i==j) dp[i][j]=dp[i][j-1]+1;
else if(i<j) dp[i][j]=dp[i][i];
else dp[i][j]=dp[i][j-1]+dp[i-j][j-1];
}
将n划分为k个数的划分数(一个划分中正好有k个数)
划分中是否包含1,包含的话就是(dp[i-1][k-1])(把1拿出来,然后将剩下的分为k-1份),不包含的话就是(dp[i-k][k])(把1分给k份,每一份都有一个1之后将剩余的分成k份,这样就可以保证每一份都不为1)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++) {
if(j==1) dp[i][j]=1;
else dp[i][j]=dp[i-1][j-1]+dp[i-j][j];
}