整数划分问题

所谓整数划分,是指把一个正整数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)当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以达到回归条件,从而解决问题。其递推表达式如下:

         f(n, m)=       1;                                (n=1 or m=1)

                            f(n, n);                         (n<m)

                            1+ f(n, m-1);                (n=m)

                            f(n-m,m)+f(n,m-1);       (n>m)

unsigned long  GetPartitionCount(int n, int max)
 {
     if (n == 1 || max == 1)
         return 1;
     else if (n < max)
         return GetPartitionCount(n, n);
     else if (n == max)
         return 1 + GetPartitionCount(n, max-1);
     else
         return GetPartitionCount(n,max-1) + GetPartitionCount(n-max, max);
 }

DP:

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

一:

1.若划分的多个整数可以相同

  设dp[i][j]为将i划分为不大于j的划分数

  (1) 当i<j时,i不能划分为大于i的数,所以dp[i][j]=dp[i][i]; 

  (2) 当i>j时,可以根据划分中是否含有j分为两种情况。若划分中含有j,划分方案数为dp[i-j][j];若划分数中不含j,相当于将i划分为不大于j-1的划分数,为dp[i][j-1]。所以当i>jdp[i][j]=dp[i-j][j]+dp[i][j-1]

  (3) 当i=j时,若划分中含有j只有一种情况,若划分中不含j相当于将i划分为不大于j-1的划分数。此时dp[i][j]=1+dp[i][j-1]

dp[n][n]可以解决问题1dp[n][k]表示将n划分为最大数不超过k的划分数,可以解决问题3

2.若划分的正整数必须不同

  设dp[i][j]为将i划分为不超过j的不同整数的划分数

  (1) 当i<j时,i不能划分为大于i的数,所以dp[i][j]=dp[i][i]

  (2) 当i>j时,可以根据划分中是否含有j分为两种情况。若划分中含有j,则其余的划分中最大只能是j-1,方案数为dp[i-j][j-1];若划分中不含j,相当于将i划分为不大于j-1的划分数,为dp[i][j-1]。所以当i>jdp[i][j]=dp[i-j][j-1]+dp[i][j-1]

  (3) 当i=j时,若划分中含有j只有一种情况,若划分中不含j相当于将i划分为不大于j-1的划分数。此时dp[i][j]=1+dp[i][j-1]

dp[n][n]表示将n划分为不同整数的划分数,可以解决问题5.

二 将n划分为k个整数的划分数

dp[i][j]为将i划分为j个整数的划分数。

  (1) i<j为不可能出现的情况,dp[i][j]=0

  (2) 若i=j,有一种情况:i可以划分为i1之和,dp[i][j]=1

  (3) 若i>j将i划分成j个数的划分法:

 dp[i][j]= dp[i-j][j]+ dp[i-1][j-1];

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

 

dp[i][j]为将i划分为j个整数的划分数,可解决问题2

三 将n划分为若干正奇数之和的划分数

f[i][j]为将i划分为j个奇数之和的划分数,g[i][j]为将i划分为j个偶数之和的划分数。

使用截边法,将g[i][j]j个划分都去掉1,可以得到f[i-j][j],所以

g[i][j] = f[i-j][j]

f[i][j]中有包含1的划分方案和不包含1的划分方案。对于包含1的划分方案,可以将1的划分除去,转化为“将i-1划分为j-1个奇数之和的划分数”,即f[i-1][j-1];对于不包含1的划分方案,可以使用截边法对j个划分每一个都去掉一个1,转化为“将i-j划分为j个偶数之和的划分数”,即g[i-j][j]

所以f[i][j]=f[i-1][j-1]+g[i-j][j]

f[n][0]+f[n][1]+……+f[n][n]为将n划分为若干奇数的划分数,为问题4的答案。

 

问题扩展1(转帖):将正整数划分成连续的正整数之和
如15可以划分成4种连续整数相加的形式:
15
7 8
4 5 6
1 2 3 4 5

思想:首先考虑一般的形式,设n为被划分的正整数,x为划分后最小的整数,如果n有一种划分,那么结果就是x,如果有两种划分,就是x和 x + 1,如果有m种划分,就是 x 、x + 1 、x + 1 、x + 2 、... 、x + m - 1,将每一个结果相加得到一个公式(i * x + i * (i - 1) / 2) = n,i为当前划分后相加的正整数个数。
满足条件的划分就是使x为正整数的所有情况。
如上例,当i = 1时,即划分成一个正整数时,x = 15

              当i = 2时, x = 7。当i = 3时,x = 4

              当i = 4时,4/9,不是正整数

              因此,15不可能划分成4个正整数相加。当i = 5时,x = 1。
    这里还有一个问题,这个i的最大值是多少?不过有一点可以肯定,它一定比n小。我们可以做一个假设,假设n可以拆成最小值为1的划分,如上例中的1 2 3 4 5。这是n的最大数目的划分。如果不满足这个假设,那么 i 一定比这个划分中的正整数个数小。因此可以得到这样一个公式i * (i + 1) / 2 <= n,(本质就是从1开始累加到i)即当i满足这个公式时n才可能被划分。

 

问题扩展2(转帖):一个数分解为N个数的和,使这N个数的积为最大

把n分成若干个3和若干个2的和,其中3尽量多,2只能是0个、1个或2个(尽量少),则他们的积为最大,具体方法为:
1)n=3k时,把n分解成k个3,此时3^k最大
2)n=3k+1=3(k-1)+2+2,把n分解成k-1个3和2个2,此时乘积3^(k-1)*4最大
3)n=3k+2,把n分解成k个3和1个2,此时乘积3^k*2最大
2的个数不多于2个,这是因为假设有3个2,但6=3+3,2^3<3^2,所以此时分解成2个3乘积更大
一般性的结论为:把自然数n分解成若干数之和,则当每一数接近无理数e时积最大

因为e=2.71828……更接近3,所以要求3尽量多,2尽量少。
下面给个证明:
由均值不等式知道,把n平分成m份时乘积比非平均分大,所以设把n平均分成x份,乘积为y,

即有y=(n/x)^x
y'=[(n/x)^x]*(lnn-lnx-1)
令y'=0得lnn-lnx-1=0,x=e^(lnn-1)=n/e,所以有每一份为n/x=e .

 

 

===================================================================================================================

另外的补充,可能有的概念和上面的说法不一,意会就好!

主要是一些定理,技巧性较高:

归类(转载):

概念:
1.n的划分:把n写成几个自然数和的形式称作n的一个划分
2.n的r划分:把n写成r个自然数和的形式称作n的一个r划分
3.n的划分数:n的不同划分的个数,为了理论上的方便这里的划分包括了这个数自己
4.n的r划分数:n的不同r划分的个数,同上一条
5.n的不可重复划分:把n写成几个不相同的自然数和的形式称作n的一个不可重复划分,这里提到这个是为了说明,n的

划分是允许重复自然数的
定理:
(这些定理一定正确,这里不做证明)                                                                        

                              

1.n的划分数=2n的n划分数
2.n的2划分数=n/2
3.n的3划分数=(n*n+3)/12
4.n的不超过k部分的划分数=n+k的k划分
5.n的最大部分不超过k的划分数=n+k的k划分
问题:
现在试图用解决对给定的n求n的划分数问题
编程实例:

#include <cstdlib>
#include <iostream>
using namespace std;
int Split(int nNum, int max)//nNum不超过max的可重复划分数 直接递归算法
{
      if(max<=0)return 0;
      if(max==1)return 1;
      if(nNum==1)return 1;
      if(nNum==0)return 1;
      int sum=0;
      for(int i=1;i<=max&&i<=nNum;i++)
      {
          sum+=Split(nNum-i,i);
      }
      return sum;
}
int p1(int r,int n)//n的r划分数 递推公式 1
{
      if(r>n)return 0;
      if(r==1)return 1;
      if(r==n)return 1;
      if(r==2)return n/2;
      if(r==3)return (n*n+3)/12;
      int sum=0;
      for(int i=1;i<=n/r;i++)
      {
          sum+=p1(r-1,n-r*i+r-1);
      }
      return sum;
}
int p2(int r,int n)//n的r划分数 递推公式2
{
      if(r>n)return 0;
      if(r==1)return 1;
      if(r==n)return 1;
      if(r==2)return n/2;
      if(r==3)return (n*n+3)/12;
      int sum=0;
      for(int i=1;i<=r;i++)
      {
          sum+=p2(i,n-r);
      }
      return sum;
}
int p(int n)//n的划分数
{
      int a[10000];
      for(int i=0;i<n;i++)a[i]=0;
      a[0]=1;
      a[1]=1;
      a[2]=2;
      if(n<0)return 0;
      for(int i=3;i<=n;i++)
      {
          int sum=0,sign=-1;
          for(int k=1;(3*k*k-k)/2<=i;k++)
          {
              sign=-sign;
              if(i-(3*k*k+k)/2>=0)sum+=sign*(a[i-(3*k*k-k)/2]+a[i-(3*k*k+k)/2]);
              else sum+=sign*a[i-(3*k*k-k)/2];
              //printf("sum=%d\n",sum);
          }
          a[i]=sum;
          //printf("a[%d]=%d\n",i,a[i]);
      }
      return a[n];
}

int main(int argc, char *argv[])
{
      cout<<Split(6,6)<<endl;
      cout<<p(6)<<endl;
      cout<<p1(2,6)<<endl;
      cout<<p2(2,6)<<endl;
      system("pause");
      return 0;
}
扩展思想:http://hi.baidu.com/hundeng/blog/item/f0c2eccaf37f8747f21fe78c.html

用f (a,b)表示把b做任意份剖分,其中最大的一个部分等于a的方案总数,用g(a,b)表示把b做任意份划分,其中最大的

一个部分不大于a的方案总数,则有:

       f (a,b)=g (a,b-a);

       g(a,b)=f(1,b)+f(2,b)+...f(a,b);


因为:

f(1,b)+f(2,b)+...f(a,b) =f(1,b)+...f(i,b) +f(a,b) (1<=i<=a-1)


f(1,b)+f(2,b)+..f(a-1,b) =g(a-1,b)

所以:

g(a,b)=f(1,b)+...f(i,b)+f(a,b)=g(a-1,b)+g(a,b-a)(1<=i<=a-1)

当b<a时,根据g(a,b)的含义,g(a,b-a)无意义。

当a=1时,显然 g(1,b)=1.

于是,根据新模型求解得到下列递推公式:

g (a,b)=g (a-1,b) b<a

g (a-1,b)+g(a,b-a) b>=a.

g(1,b)=1.

最后的g (k,n-k)即为所求。

 

问题延伸:对整数n划分m份,分别输出这m份的各个组合(转载csdn上的高手的程序,思路极其巧妙,编程技巧极高)

#include<stdio.h>
#include<stdlib.h>
int n; //你表示要拆分的元素个数
//M是要拆分的数,N是要拆分的元素个数,tmp[]用于存放元素
//divide函数每运行一次填充一个tmp[]位置,存放的时候为了方便,从最后一个开始存放,当存放到tmp[1]时打印所

有的分解值
//tmp【0】中存放的是每次分解的最小基数,tmp【0】要及时更新。同时分解的序列的值是逐步增加的,和if(M-i<i)

一起保证不会发生有重叠的发生
void divide(int M, int N, int* tmp)
{
    int i;
    if(1 == N)
    {
        tmp[N] = M;
        for(i=n; i>0; i--)
        {
            printf("%d ",tmp[i]);
        }
        printf("\n");
        return;
    }
    else
    {
        for(i = tmp[0]; i<=M-N+1; i++)
        {
            if(M-i<i) return;
            tmp[N]=i;
            tmp[0]=tmp[N];
            divide(M-i,N-1,tmp);
        }
    }
}

int main()
{
    int M;
    int *tmp;
    printf("input M n:");
    scanf("%d",&M);
    scanf("%d",&n);
    if (n<=0) return 0;
    tmp = (int*)malloc(sizeof(int)*(n+1));//tmp[1]--tmp[n]存放分解后的值
    tmp[0] = 1; //分解的最小基数
    divide(M,n,tmp);
    system("pause");
    return 0;
}

总结:为什么要从最后一个存放:由于递归,每次都有一个参数N,适合把数存好,省去了从前到后下标计算的麻烦。

          为什么设置N[0]为最小基数:1 是小表从后向前对应存储方便,所以设置了一个0下标而多设置了一个空间

                                                    2 基下标存储基数每次都动态变化,目的是以后进行的数据

存储都在基数之上,保

                                                       证了递增,从而避免了重复现象。

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值