博客原地址: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]可以分成以下两类: 那么看一到例题吧
POJ 1664 放苹果
其实,放苹果问题,因为可以允许有空盘子,一个盘子里的苹果最多不超过k个盘子数,所以这个问题就可以转换成数i分成最大数不超过k的划分数
贴一下AC代码吧
#include
#include
#include
#include
#include
#include
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
第三个问题解决之后,也可以解决第一个问题了,就是把最大数不超过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];
}
}
最后一个就不同说了。。