• 整数划分问题-解法汇总(暂有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
    ", 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 是相同的描述

     

     


     

    他坐在湖边,望向天空,她坐在对岸,盯着湖面
  • 相关阅读:
    【BZOJ2525】[Poi2011]Dynamite 二分+树形DP
    【BZOJ2560】串珠子 状压DP+容斥
    【BZOJ2726】[SDOI2012]任务安排 斜率优化+cdq分治
    [NOIP2017]宝藏 状压DP
    [NOIP2017]逛公园 最短路+拓扑排序+DP
    [NOIP2017]列队 离线+SBT
    【CF628D】Magic Numbers 数位DP
    【BZOJ2791】[Poi2012]Rendezvous 倍增
    sql 通过游标 拆分xml结构
    sql字符转换函数大全
  • 原文地址:https://www.cnblogs.com/Inkblots/p/4854227.html
Copyright © 2020-2023  润新知