zoukankan      html  css  js  c++  java
  • DP专题:划分数问题

    一、这个专题有什么用

    练练DP

    练练组合数学

    ......

    二、正题

    此类问题有如下几种形态:

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

    1:将n划分成若干正整数的划分数

    (1):划分数可以存在相同的数

    那么,设dp[n][m]表示整数 n 的划分中,每个数不大于 m 的划分数。

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

    总递推式:dp[n][m]=dp[n][m-1]+dp[n-m][m]

    (2):划分数不可以存在相同的数

    若还是设成同样的状态,从(1)可以看出,a条件是不会改变的,而b条件中,n-m后由于不存在重复,则划分数变为dp[n-m][m-1]

    总递推式:dp[n][m]=dp[n][m-1]+dp[n-m][m-1]

    2:将n划分成k个正整数的划分数

    设dp[i][k]为,把i分成k分的划分法

    那么也有两种情况:

      a.n份中不包括1的情况,那么每份中都可以拿出1来,即为dp[i-k][k]

      b.n份中起码有1个1的情况,那么把这个1去掉,变为dp[i-1][k-1]

    总递推式:dp[i][k]=dp[i-k][k]+dp[i-1][k-1]

    3:将n划分成若干奇数的划分数

    设g[i][j]:将i划分成j个偶数

    f[i][j]:将i划分成j个奇数

    对于偶数来讲,为f[i-j][j]中的共j个数,每个加1,则变成偶数。

    对于奇数来讲,为g[i-j][j]中的共j个数,每个加1,则变成奇数;且还有新加入的1,则为f[i-1][j-1]

    所以递推式为:g[i][j]=f[i-j][j] , f[i][j]=f[i-1][j-1]

    可以看一看HitOJ1402的代码:

     1 /*
     2  * hit1402.c
     3  *
     4  *  Created on: 2011-10-11
     5  *      Author: bjfuwangzhu
     6  */
     7 
     8 #include<stdio.h>
     9 #include<string.h>
    10 #define nmax 51
    11 int num[nmax][nmax]; //将i划分为不大于j的个数
    12 int num1[nmax][nmax]; //将i划分为不大于j的不同的数
    13 int num2[nmax][nmax]; //将i划分为j个数
    14 int f[nmax][nmax]; //将i划分为j个奇数
    15 int g[nmax][nmax]; //将i划分为j个偶数
    16 void init() {
    17     int i, j;
    18     for (i = 0; i < nmax; i++) {
    19         num[i][0] = 0, num[0][i] = 0, num1[i][0] = 0, num1[0][i] = 0, num2[i][0] =
    20                 0, num2[0][i] = 0;
    21     }
    22     for (i = 1; i < nmax; i++) {
    23         for (j = 1; j < nmax; j++) {
    24             if (i < j) {
    25                 num[i][j] = num[i][i];
    26                 num1[i][j] = num1[i][i];
    27                 num2[i][j] = 0;
    28             } else if (i == j) {
    29                 num[i][j] = num[i][j - 1] + 1;
    30                 num1[i][j] = num1[i][j - 1] + 1;
    31                 num2[i][j] = 1;
    32 
    33             } else {
    34                 num[i][j] = num[i][j - 1] + num[i - j][j];
    35                 num1[i][j] = num1[i][j - 1] + num1[i - j][j - 1];
    36                 num2[i][j] = num2[i - 1][j - 1] + num2[i - j][j];
    37             }
    38         }
    39     }
    40     f[0][0] = 1, g[0][0] = 1;
    41     for (i = 1; i < nmax; i++) {
    42         for (j = 1; j <= i; j++) {
    43             g[i][j] = f[i - j][j];
    44             f[i][j] = f[i - 1][j - 1] + g[i - j][j];
    45         }
    46     }
    47 }
    48 int main() {
    49 #ifndef ONLINE_JUDGE
    50     freopen("data.in", "r", stdin);
    51 #endif
    52     int n, k, i, res0, res1, res2, res3, res4;
    53     init();
    54     while (~scanf("%d %d", &n, &k)) {
    55         res0 = num[n][n];
    56         res1 = num2[n][k];
    57         res2 = num[n][k];
    58         for (i = 0, res3 = 0; i <= n; i++) {
    59             res3 += f[n][i];
    60         }
    61         res4 = num1[n][n];
    62         printf("%d
    %d
    %d
    %d
    %d
    
    ", res0, res1, res2, res3, res4);
    63     }
    64     return 0;
    65 }
    View Code

    4.将正整数划分成连续的正整数之和

    eg:15可以划分成4种连续整数相加的形式

    15

    7 8

    4 5 6

    1 2 3 4 5

    首先考虑一般形式,设n为被划分的正整数,x为划分后的最小整数,如果n有一种m划分,那么数列为

    x,x+1,x+2....x+m-1

    而其结果的公式为( x * i + i * ( i + 1 ) / 2 )

    满足条件的划分就是使x为正整数的所有情况。

    如上例,当i = 1时,即划分成一个正整数时,x = 15, 当i = 2时, x = 7。

    当x = 3时,x = 4, 当x = 4时,4/9,不是正整数,因此,15不可能划分成4个正整数相加。

    当x = 5时,x = 1。

    这里还有一个问题,这个i的最大值是多少?

    有一点可以肯定,它一定比n小。我们可以做一个假设,
    假设n可以拆成最小值为1的划分,如上例中的1 2 3 4 5。这是n的最大数目的划分。如果不满足这个假设,
    那么 i 一定比这个划分中的正整数个数小。因此可以得到这样一个公式i * (i + 1) / 2 <= n,即当i满足
    这个公式时n才可能被划分。

    代码如下

     1 void split(int n) {
     2     int i, j, te, x, xlen;
     3     for (i = 1, xlen = 0; (te = i * (i - 1) / 2) < n; i++) {
     4         x = n - te;
     5         if (x % i == 0) {
     6             x /= i;
     7             printf("%d", x);
     8             for (j = 1; j < i; j++) {
     9                 printf("%d ", x + j);
    10             }
    11             printf("
    ");
    12             xlen++;
    13         }
    14     }
    15     printf("%d
    ", xlen);
    16 }
    View Code

    5.求划分因子乘积最大的一个划分及此乘积

    问题简述:

    给定一个正整数n, 则在n所有的划分中, 求因子乘积最大的一个划分及此乘积。例如:8 = {8}, {7, 1}, {6, 2}, {5, 3}, {4, 4}, {3, 3, 2}, {2, 2, 2, 2} 等,那么在这些当中,3 * 3 * 2 的乘积最大,所以输出整个划分和这个乘积 18。
      

    算法分析:

    这是我在某个论坛上看到的问题,以及别人针对此问题的数学分析,现简单的整理如下:
    (1)对于任意大于等于4的正整数m, 存在一个划分m = m1+m2, 使 m1*m2 >= m

      证: 令m1 = int(m/2), 则 m1 >= 2 , m2 = m-m1; 那么m2 > 2,并且 m2 >= m/2 >= m1;    m1*m2 >= 2*m2 >= m; 证毕;
      该证明简单的来说就是:对于一个大于等于4的正整数m,存在一个2块划分的因子,这两个因子的乘积总是不小于原数m本身。
    (2)由(1)知此数最终可以分解为 2^r * 3^s。现证明 r <= 2;
      证:若r > 2, 则至少有3个因子为2, 而2*2*2 < 3*3;
      所以可以将3个为2的因子,换为两个因子3;积更大;证毕。
    综合(1),(2),则有:任何大于4的因子都可以有更好的分解, 而4可以分解为2*2。
    所以:此数应该分解为 2^k1 * 3^k2。而且可以证明 k1>=0 并且 k1 <= 2,因此:
         A.当n = 3*r 时, 分解为 3^r
         B.当n = 3*r+1时, 分解为 3^(r-1)*2*2
         C.当n = 3*r+2时, 分解为 3^r*2
    剩下编程处理,那就是太简单了,首先是处理 <= 4的特殊情况,再对>4的情况进行模3的3种情况的判断,最后一一输出。

    可见,数学在整数划分问题上有太强的功能。谁叫这个问题叫整数划分呢,不与数学密切才怪! ^_^。

    6.从小学奥数的整数划分得到的有用结论

    例1:把14分拆成若干个自然数的和,再求出这些数的积,要使得到的积最大,应该把14如何分拆?这个最大的乘积是多少?

    :我们先考虑分成哪些数时乘积才能尽可能地大。
      首先,分成的数中不能有1,这是显然的。
      其次,分成的数中不能有大于4的数,否则可以将这个数再分拆成2与另外一个数的和,这两个数的乘积一定比原数大,例如7就比它分拆成的2和5的乘积小。
      再次,因为4=2×2,故我们可以只考虑将数分拆成2和3。
      注意到2+2+2=6,2×2×2=8;3+3=6,3×3=9,因此分成的数中若有三个2,则不如换成两个3,换句话说,分成的数中至多只能有两个2,其余都是3。根据上面的讨论,我们应该把14分拆成四个3与一个2之和,即14=3+3+3+3+2,这五数的积有最大值 3×3×3×3×2=162。
      将上述结论推广为一般情形便是:
      把自然数S(S>1)分拆为若干个自然数的和:   S=a1+a2+…+an,则当a1,a2,…,an中至多有两个2,其余都是3时,其连乘积m=a1a2…an有最大值。

    例2:把1993分拆成若干个互不相等的自然数的和,且使这些自然数的乘积最大,该乘积是多少?

    解:由于把1993分拆成若干个互不相等的自然数的和的分法只有有限种,因而一定存在一种分法,使得这些自然数的乘积最大。

      若1作因数,则显然乘积不会最大。把1993分拆成若干个互不相等的自然数的和,因数个数越多,乘积越大。为了使因数个数尽可能地多,我们把1993分成2+3…+n直到和大于等于1993。

          若和比1993大1,则因数个数至少减少1个,为了使乘积最大,应去掉最小的2,并将最后一个数(最大)加上1。

          若和比1993大k(k≠1),则去掉等于k的那个数,便可使乘积最大。

          所以n=63。因为2015-1993=22,所以应去掉22,把1993分成(2+3+…+21)+(23+24+…+63)这一形式时,

      这些数的乘积最大,其积为  2×3×…×21×23×24×…×63。

    以上内容转自 qingyezhu的cnblogs  并加以改编

  • 相关阅读:
    UVA12206 Stammering Aliens 【SAM 或 二分 + hash】
    字符串hash
    BZOJ1064 [Noi2008]假面舞会 【dfs】
    BZOJ2333 [SCOI2011]棘手的操作 【离线 + 线段树】
    BZOJ1499 [NOI2005]瑰丽华尔兹 【单调队列优化dp】
    BZOJ3343 & 洛谷2801:教主的魔法——题解
    BZOJ2821:作诗——题解
    BZOJ2724:[Violet 6]蒲公英——题解
    BZOJ2653:middle——题解
    BZOJ2588:Count on a tree——题解
  • 原文地址:https://www.cnblogs.com/ibilllee/p/7655317.html
Copyright © 2011-2022 走看看