整数划分问题-解法汇总(暂有DP-递归)

整数划分问题是一个锻炼组合数学,递归以及动态规划很好的例子,虽然问题看似简单,但是其中玄机万千,有人转化成为背包问题,有人用生成函数解,有人以此作为企业面试题目,可见这种问题的认可度还是很高的。

整数划分问题可以出现很多变种问题:

  1. 将n划分成若干正整数之和的划分数。(原题)
  2. 将n划分成k个正整数之和的划分数。
  3. 将n划分成最大数不超过k的划分数。
  4. 将n划分成若干奇正整数之和的划分数。
  5. 将n划分成若干不同整数之和的划分数......

第2,3,5的Code在北大研究生推免机试(校内)-复杂的整数划分中放出

具体来说,什么是整数划分呢?

  • 假设有一个整数 n , 总存在 n=m1+m2+...+mi; (mi为正整数,且1 <= mi <= n),则{m1,m2,...,mi}为n的一个划分。

 


 

 

∞这里我先用动态规划和递归的思想来解答该问题:

  • 我们定义一个状态如下: 当max(m1,m2,...,mi)<=m,那么称这一状态属于n的一个m划分。这里我们记n的m划分的个数为f(n,m);
  • 举个例子,当n=5时我们可以获得以下这几种划分(注意,例子中m>=5)

5 = 5        ————f(5,5)
   = 4 + 1       ————f(5,4)
   = 3 + 2           |
   = 3 + 1 + 1         ————f(5,3)
   = 2 + 2 + 1           |
   = 2 + 1 + 1 + 1         ————f(5,2)
   = 1 + 1 + 1 + 1 + 1         ————f(5,1)

 

根据n和m的关系,考虑以下几种情况:
  1. 当n=1时,只有一种划分{1};
  2. 当m=1时,只有一种划分{1,1,1,...,1};
  3. 当n=m时,根据划分中是否包含n,可分为两种情况:
      (1) 划分中包含n的情况,只有一个即{n};
      (2) 划分中不包含n的情况,这时划分中最大的数字也一定比n小,即n的所有(n-1)划分。

      因此 f(n,n) =1 + f(n,n-1);
  4. 当n<m时,划分总等同于f(n,n);
  5. 但n>m时,根据划分中是否包含最大值m,可以分为两种情况:
      (1) 划分中包含m的情况,即{m, {x1,x2,...xi}}, 其中{x1,x2,...xi} 的和为n-m,由于m>n-m,因此是(n-m)的m划分,因此这种划分个数为f(n-m, m);
      (2) 划分中不包含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以达到回归条件,从而解决问题。其递推表达式如下:

    • f(n, m)= 1;      (n=1 or m=1)
    • f(n, m)=f(n, n);   (n<m)
    • 1+ f(n, m-1);      (n=m)
    • f(n-m,m)+f(n,m-1); (n>m)

 

 

依照上述解法,就能够写出如下Code了:

  

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 using namespace std;
 5 
 6 #define MAX 51
 7 
 8 int dp[MAX][MAX];
 9 
10 void DP()
11 {
12     for (int n = 1; n < MAX; n++)
13     {
14         for (int m = 1; m < MAX; m++)
15         {
16             if (n < m)
17                 dp[n][m] = dp[n][n];
18             else if (n > m)
19                 dp[n][m] = dp[n - m][m] + dp[n][m - 1];
20             else
21                 dp[n][m] = 1 + dp[n][m - 1];
22         }
23     }
24 }
25 
26 int main()
27 {
28     int num;    //输入整数
29     DP();
30     while (scanf("%d", &num) != EOF)
31         printf("%d\n", dp[num][num]);
32 
33     return 0;
34 }
小墨-原创

 


 

 

其他变种:

 

1.将n划分成不大于m的划分法: 

   1).若是划分多个整数可以存在相同的:

       dp[n][m]= dp[n][m-1]+ dp[n-m][m]

      dp[n][m]表示整数 n 的划分中,每个数不大于 m 的划分数。
       则划分数可以分为两种情况:
       a.划分中每个数都小于 m,相当于每个数不大于 m- 1, 故划分数为 dp[n][m-1].
       b.划分中有一个数为 m. 那就在 n中减去 m ,剩下的就相当于把 n-m 进行划分, 故划分数为 dp[n-m][m];

   2).若是划分多个不同的整数:

      dp[n][m]= dp[n][m-1]+ dp[n-m][m-1]

       dp[n][m]表示整数 n 的划分中,每个数不大于 m 的划分数。
      同样划分情况分为两种情况:
      a.划分中每个数都小于m,相当于每个数不大于 m-1,划分数为 dp[n][m-1].
      b.划分中有一个数为 m.在n中减去m,剩下相当对n-m进行划分,

   并且每一个数不大于m-1,故划分数为 dp[n-m][m-1]

   3).将n划分成k个数的划分法:

   dp[n][k]= dp[n-k][k]+ dp[n-1][k-1];

     方法可以分为两类讨论:
       第一类: n 份中不包含 1 的分法,为保证每份都 >= 2,可以先拿出 k 个 1 分
     到每一份,然后再把剩下的 n- k 分成 k 份即可,分法有: dp[n-k][k]
       第二类: n 份中至少有一份为 1 的分法,可以先拿出一个 1 作为单独的1份,剩
     下的 n- 1 再分成 k- 1 份即可,分法有:dp[n-1][k-1]

   4).将n划分成奇正整数的划分法:

   只需将1)中第二重循环的m++改成m+=2,因为 <=某偶数<= 某偶数-1 是相同的描述

 

 


 

posted @ 2015-10-04 10:43 文字失效 阅读(...) 评论(...) 编辑 收藏