对于简单的正整数划分只需要divide(n,m),把n数做m的划分,即n划分成的k份中,每份的值都小于m. 可以参考ACM题解——正整数划分
#复杂的正整数划分 简单的正整数划分是针对n个数划分的所有可能。那么如果题目有要求划分的条件,那么状态转移方程将有所不同。
可能出现的划分要求有:
- 将n划分成若干正整数之和的划分数。(简单正整数划分)
- 将n划分成k个正整数之和的划分数。
- 将n划分成最大数不超过k的划分数。
- 将n划分成若干奇正整数之和的划分数。
- 将n划分成若干不同整数之和的划分数。
不同情况下的状态转移方程
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];
(因为划分的整数可以相同,所以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].
(由于划分出的每份整数要求不同,所以n-m划分的最大上限是m-1)
2.将n划分成k个数的划分法:(对划分分数做分类,状态量是需要划分的数以及划分的份数)
dp[n][k]= dp[n-k][k]+ dp[n-1][k-1];
含义:dp[n][k],表示整数n的划分中,需要划分k份
方法可以分为两类:(分出的k份中是否包含值为1的份)
第一类: n 份中不包含 1 的分法。
为保证每份都 >= 2,可以先拿出 k 个 1 分。到每一份,然后再把剩下的 n- k 分成 k 份即可,分法有: dp[n-k][k]
(剩下n-k的值还要再分k分和原来拿出的k份1合并,使得每份必>1(即>=2))
第二类: n 份中至少有一份为 1 的分法。
可以先拿出一个 1 作为单独的1份,剩下的 n- 1 再分成 k- 1 份即可,分法有:dp[n-1][k-1]
(已经分出完成的一份1了,剩下n-1的值还需要分k-1份就可以)
3.将n划分成若干奇数的划分法:(计算奇数划分时候同时考虑偶数,状态量为需要划分的数和划分的份数)
方法一:
g[i][j] = f[i - j][j];
f[i][j] = f[i - 1][j - 1] + g[i - j][j];
含义:g[i][j]:将i划分为j个偶数
f[i][j]:将i划分为j个奇数
方法可以分为两个步骤:
第一步:g[i][j] = f[i - j][j];
i中拿出j个1分到每一份中,将剩余的i-j分成j个奇数
(这j个奇数和原来拿出的1变成j份偶数。分i为j个偶数(用g表示分偶数)=分i为1+j个奇数=分i-j为j个奇数(用f表示分奇数))
第二步:将i划分为j个奇数,等同于两种子问题的和
a.j份中包含有一份为1的情况:一份包含奇数1,剩余的i-1分成j-1个奇数(f表示分成奇数值);
b.j份中不包含一份为1的情况:每份至少大于1,将j个1拿出来分到每一份中,其余i-j分成j份的偶数(j份的偶数和原来j份的1形成j份奇数f[i][j])
方法二:
含义:f[i][j]表示数i划分成j个正奇数的方案数
初始值:
对于i为奇数 f[i][1]=1;f[i][2]=0;
对于i为偶数 f[i][1]=0;f[i][2]=(i/2+1)/2;
考虑f[i][j],需满足i>=j,j>2时
若最小的正奇数是1即j份中包含有一份为1的情况。
对应的方案数是 f[i-1][j-1]
若最小的正奇数>1即j份中不包含一份为1的情况。
从每个正整数中抽调2,对应的方案数f[i-2*j][j];
(为什么要抽2个给每一份,而不是1个,原因是抽两个后还是把i-2*j的值分成j份的奇数值。如果凑1个给每份,就要引入分成j份的偶数值,也就是方法一中的g的含义)
状态转移方程:f[i][j]=f[i-1][j-1]+f[i-2*j][j];
对应代码如下:
for (int i=1;i<=n;i++){
if (i%2)
{
f[i][1]=1;
f[i][2]=0;
}
else
{
f[i][1]=0;
f[i][2]=(i/2+1)/2;
}
}
for (int i=3;i<=n;i++){
for (int j=1;j<=i;j++)
{
f[i][j]=f[i-1][j-1];
if (i-2*j>=j)
f[i][j]+=f[i-2*j][j];
}
}
for (int i=1;i<=n;i++)
ans+=f[n][i];
cout<<ans<<endl;//输出所有i分奇数组合的可能
//如果是i分成k份奇数值,那么直接输出F[i][k]
}
类似题目也可以考虑用母函数来解决。