整数划分总结


博客原地址:https://blog.csdn.net/dacc123/article/details/50664738

整数划分问题:

笼统上说就是将一个整数划分成若干个整数之和的方案数。整数划分有很多不同的问法,也有比较隐晦的问法。比如n个苹果放到m个盘子里,比如n个砖块堆成m个层阶梯。关于整数划分,大概有以下这么多扩展的问题:

1、整数n划分成若干整数之和的方案数;

2、整数n划分成k个整数之和的方案数;

3、整数n划分成最大数不超过k的若干整数之和的方案数;

4、整数n划分成最小数不低于k的若干整数之和的方案数;

5、整数n划分成若干不同的整数之和的方案数;

6、整数n划分成k个不同整数之和的方案数;

7、整数n划分成最大数不超过k的若干不同整数之和的方案数;

8、整数n划分成最小数不低于k的若干不同整数之和的方案数;

9、整数n划分成若干奇数的方案数;

10、整数n划分成若干偶数的方案数;


这么多问题一个一个解决,其实解决其中一个问题,相关联的问题也迎刃而解。首先看第二个问题,为什么不看第一个问题呢?很简单仔细思考一下,枚举所有的k不就是第一个问题的解嘛。第二个问题的状态转移方程式 引入dp2[][]表示整数i分成j个整数的方案数。

        memset(dp2,0,sizeof(dp2));
        dp2[0][0]=1;
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=i;j++)
            {
                dp2[i][j]=dp2[i-1][j-1]+dp2[i-j][j];
            }
        }

怎么去解释这个状态转移方程呢?你可以把整数i分成j个整数之和看成i个苹果放到j个盘子里,不允许有空盘子。j个盘子,可以分成两种状态,所有盘子里最小的苹果数等于1,和大于1。等于1的情况,把这个最小苹果数的盘子拿掉,dp[i][j]=dp[i-1][j-1];若最小数大于1,那么把所有j的盘子都拿去一个苹果,dp[i][j]=dp[i-j][j]。那么第一个问题就也解决了。


现在看第三个问题,最大数不超过k的划分数

        memset(dp3,0,sizeof(dp3));
        dp3[0][0]=1;
        for(int i=0;i<=n;i++)
        {
            for(int j=1;j<=n;j++)
            {
                if(i>=j)
                    dp3[i][j]=dp3[i][j-1]+dp3[i-j][j];
                else
                    dp3[i][j]=dp3[i][j-1];
            }
        }
解释这个状态转移方程:dp[i][j]表示i分成最大数不超过j的划分数。dp[i][j]可以分成以下两类: 
1.最大数等于j; 
2.最大数小于j; 
对于第二种情况,满足划分条件的是dp[i][j-1],表示数i分成最大数不超过j-1的划分数,那就是数i分成最大数小于j的划分数。第一种情况,满足划分条件的是dp[i-j][j];dp[i-j][j]表示数i-j分成最大数不超过j的划分数,然后将‘+j’附加在每一个划分后面,就得到数分成最大数等于j的划分数。那么dp[i-j][j]的划分数就等于dp[i][j]的划分。 

那么看一到例题吧

POJ 1664 放苹果

其实,放苹果问题,因为可以允许有空盘子,一个盘子里的苹果最多不超过k个盘子数,所以这个问题就可以转换成数i分成最大数不超过k的划分数 

贴一下AC代码吧

#include <iostream>
#include <algorithm>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <stdio.h>

using namespace std;
int dp[15][15];
int n,m;
void fun()
{

    for(int i=1;i<=11;i++)
    {
        dp[i][1]=1;
        //dp[i][0]=0;
       for(int j=1;j<=11;j++)
       {
           dp[0][j]=1;
           if(i<j)
            dp[i][j]=dp[i][j-1];
           else
            dp[i][j]=dp[i][j-1]+dp[i-j][j];
       }
    }
}
int main()
{
    memset(dp,0,sizeof(dp));
    fun();
    int t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&m);

        printf("%d\n",dp[n][m]);
    }
}

第三个问题解决之后,也可以解决第一个问题了,就是把最大数不超过k的k从1到n枚举一下就可以了。

第四个问题和第三个问题很相似,先看状态转移方程

                memset(dp4,0,sizeof(dp4));
                for(int i=0;i<=n;i++)
                    dp4[0][i]=1;
                for(int i=1;i<=n;i++)
                {
                    for(int j=n;j>=1;j--)
                    {
                        if(i>=j)
                            dp4[i][j]=dp4[i][j+1]+dp4[i-j][j];
                        else
                            dp4[i][j]=dp4[i][j+1];
                    }
                }


同样的,dp[i][j]可以分成两大类 
1.最小数等于j; 
2.最小数大于j; 
满足第二个情况的划分条件是dp[i][j+1],表示数i分成最小数大于j的情况。满足第一个情况的划分条件是dp[i-j][j],表示i-j的最小数不小于j的划分,然后将‘+j’附加在每一个划分后面,就得到数i分成最小数等于j的划分。同理枚举k可以得出第一个问题的解

第五个问题,到这里可以看出前四个是一个类型,后面四个是另一个类型,后面多出一个要求,就是要求不同的数字。同理第五个问题是由第六个,七个,八个问题求解出来。 
那么直接看第六个问题,这里我们和前面的联系起来看,因为只多了一个数字不同的条件 
先看状态转移方程

                memset(dp6,0,sizeof(dp6));
                dp6[0][0]=1;
                for(int i=1;i<=n;i++)
                {
                    for(int j=i;j>=1;j--)
                    {
                        dp6[i][j]=dp6[i-j][j]+dp6[i-j][j-1];
                    }
                }

可以看出和第二个问题状态转移方程的区别就是一点,dp[i][j-1]变成dp[i-j][j-1]。这里我们不用放苹果,我们用i个砖块堆成有j个阶次的阶梯,阶梯要求不能有相邻两个是同样的高度 


状体转移方程可以描述为,把阶梯最底层的一层砖块全部拿掉,阶梯要么还是不变的阶数,要么阶数少了一阶。 
所以dp[i][j]=dp[i-j][j]+dp[i-j][j-1]; 
第五个问题直接枚举j就好了。 

这里有一道例题,就是明白着的问题六 

HOJ 1090

第七个问题,直接看状态转移方程

            memset(dp7,0,sizeof(dp7));
                dp7[0][0]=1;
                for(int i=0;i<=n;i++)
                {
                    for(int j=1;j<=n;j++)
                    {
                        if(i>=j)
                            dp7[i][j]=dp7[i][j-1]+dp7[i-j][j-1];
                        else
                            dp7[i][j]=dp7[i][j-1];
                    }
                }

和问题四的区别就是dp[i-j][j]变成了dp[i-j][j-1] 
同样的问题分成两类,第二类是不变的dp[i][j-1],第一类变成了dp[i-j][j-1],对于第一类,dp[i-j][j-1]表数i-j分成最大数不超过j-1的划分数,也就是i-j分成最大数小于j,将‘+j’附加在每个划分后面,得到数i分成最大数等于j的划分。这样就避免了有重复的,因为dp[i-j][j]表示最大数不超过j,就有等于j的情况,那么再加上j就有重复的情况了。

第八个问题,就应该很好理解了 
附代码

memset(dp8,0,sizeof(dp8));
        for(int i=0;i<=n+1;i++)
            dp8[0][i]=1;
        for(int i=1;i<=n;i++)
        {
            for(int j=n;j>=1;j--)
            {
                if(i>=j)
                    dp8[i][j]=dp8[i][j+1]+dp8[i-j][j+1];
                else
                    dp8[i][j]=dp8[i][j+1];
            }
        }

第九问题,分成若干个奇数

只需要判断一下奇偶性就好了

代码

      memset(dp9,0,sizeof(dp9));
        dp9[0][0]=1;
        for(int i=0;i<=n;i++)
        {
            for(int j=1;j<=n;j++)
            {
                if(i>=j)
                {
                    if(j%2==1)
                        dp9[i][j]=dp9[i][j-1]+dp9[i-j][j];
                    else
                        dp9[i][j]=dp9[i][j-1];
                }
                else
                    dp9[i][j]=dp9[i][j-1];
            }
        }

          最后一个就不同说了。。

  • 11
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值