整数划分问题:
(一)整数M划分为任意个整数的方案数量;
(二)整数M划分为任意个不同整数的方案数量。
动态规划:
动态规划是当前因子可以由前序若干个因子推导出来情况,根据推导公式来依次求出当前因子及后续因子的方法。关键在于找到当前因子与前序因子之间的固定推导公式。还需要找到一些边界值,来确定初始因子。
下面用动态规划的方法分析整数划分问题,我们用dp(N,M)表示整数M划分为N个整数的方案数量。dp(i,j)的前序因子为dp(1,1)、dp(1,2)、……、dp(1,j)、dp(2,1)、dp(2,2)、……、dp(2,j)、……、dp(i-1,j)、dp(i,j-1)。
(一)整数M划分为任意个整数(可重复)
假设dp(i,j)有下面所列的划分方案:
1,xxxxxx
1,xxxxxx
......
1,xxxxxx
-------------------------
2,xxxxxx
2,xxxxxx
......
x,xxxxxx
横线以上为包含数字1的划分方案情况,横线下方为不包含数字1的划分情况。包含1的情况,如果去掉组合中的第一个1,即dp(i-1,j-1)。
不包含1的情况,组合中的最小数为2,给每个方案的每一位数字减1,即dp(i,j-i);
由此可确定推导公式:dp(i,j)=dp(i-1,j-1)+dp(i,j-i);
再根据常识推导出一些边界值的公式:
dp(0,0)=1; dp(0,j)=0(j>0); dp(1,j)=1(j>0); dp(j,j)=1; dp(i,j)=0(i<j); dp(i,j)=1(i=j-1)。
第一个边界值dp(0,0)可以推导出后续的所有因子,而后面的边界值可以在程序中辅助减少计算的时间。
例如M=10,开始从dp(1,1)依次向后推导,可以将每个推导结果存在二维数组中,方便推导后续节点:
j=0 | j=1 | j=2 | j=3 | j=4 | j=5 | j=6 | j=7 | j=8 | j=9 | j=10 | |
i=0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
i=1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
i=2 | 0 | 0 | 1 | dp(2,3)= dp(1,2)+ dp(2,1) =1+0=1 | dp(2,4)= dp(1,3)+ dp(2,2) =1+1=2 | dp(2,5)= dp(1,4)+ dp(2,3) =1+1=2 | dp(2,6) =dp(1,5)+ dp(2,4) =1+2=3 | dp(2,7)= dp(1,6)+ dp(2,5) =1+2=3 | dp(2,8)= dp(1,7)+ dp(2,6) =1+3=4 | dp(2,9)= dp(1,8)+ dp(2,7) =1+3=4 | dp(2,10)= dp(1,9)+ dp(2,8) =1+4=5 |
i=3 | 0 | 0 | 0 | 1 | dp(3,4)= dp(2,3)+ dp(3,1) =1+0=1 | dp(3,5)= dp(2,4)+ dp(3,2) =2+0=2 | dp(3,6)= dp(2,5)+ dp(3,3) =2+1=3 | dp(3,7)= dp(2,6)+ dp(3,4) =3+1=4 | dp(3,8)= dp(2,7)+ dp(3,5) =3+2=5 | dp(3,9)= dp(2,8)+ dp(3,6) =4+3=7 | dp(3,10)= dp(2,9)+ dp(3,7) =4+4=8 |
i=4 | 0 | 0 | 0 | 0 | 1 | dp(4,5)= dp(3,4)+ dp(4,1) =1+0=1 | dp(4,6)= dp(3,5)+ dp(4,2) =2+0=2 | dp(4,7)= dp(3,6)+ dp(4,3) =3+0=3 | dp(4,8)= dp(3,7)+ dp(4,4) =4+1=5 | dp(4,9)= dp(3,8)+ dp(4,5) =5+1=6 | dp(4,10)= dp(3,9)+ dp(4,6) =7+2=9 |
i=5 | 0 | 0 | 0 | 0 | 0 | 1 | dp(5,6)= dp(4,5)+ dp(5,1) =1+0=1 | dp(5,7)= dp(4,6)+ dp(5,2) =2+0=2 | dp(5,8)= dp(4,7)+ dp(5,3) =3+0=3 | dp(5,9)= dp(4,8)+ dp(5,4) =5+0=5 | dp(5,10)= dp(4,9)+ dp(5,5) =6+1=7 |
i=6 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | dp(6,7)= dp(5,6)+ dp(6,1) =1+0=1 | dp(6,8)= dp(5,7)+ dp(6,2) =2+0=2 | dp(6,9)= dp(5,8)+ dp(6,3) =3+0=3 | dp(6,10)= dp(5,9)+ dp(6,4) =5+0=5 |
i=7 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | dp(7,8)= dp(6,7)+ dp(7,1) =1+0=1 | dp(7,9)= dp(6,8)+ dp(7,2) =2+0=2 | dp(7,10)= dp(6,9)+ dp(7,3) =3+0=3 |
i=8 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | dp(8,9)= dp(7,8)+ dp(8,1) =1+0=1 | dp(8,10)= dp(7,9)+ dp(8,2) =2+0=2 |
i=9 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | dp(9,10)= dp(8,9)+ dp(9,1) =1+0=1 |
i=10 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
(二)整数划分为不同的整数之和。
还是上面的方案例子,不同之处在于横线上方的方案去掉1之后,后面的整数>=1,是dp(i-1,j-1)不包括1的所有方案,需要减去以1开头的所有方案数量,即dp(i-1,j-1)-dp(i-1,j-1)(方案包含1)。横线下方的方案数量仍然是dp(i,j-i)。
横线上方的方案,去掉1之后,后面的i-1个数字都大于1,让每个数字-1后,即dp(i-1)(j-1-(i-1))=dp(i-1)(j-i)。
所以整数划分为不同整数之和的动态推导公式为:dp(i,j)=dp(i-1)(j-i) + dp(i,j-i)
1,xxxxxx
1,xxxxxx
......
1,xxxxxx
-------------------------
2,xxxxxx
2,xxxxxx
......
x,xxxxxx
例如M=10
动态推导公式:dp(i,j)=dp(i-1)(j-i) + dp(i,j-i)
边界值:
dp(0,0)=1; dp(1,j)=1; dp(1,j)=0(j>0); dp(i,j)=0(j<(i*(i+1)/2)),dp(i,j)=1(j=(i*(i+1)/2)),(因为i个不同数的最小值为1+2+3+...+(i-1)+i的等差数列,和为i*(i+1)/2)。
下表直接写0,1标红的都是根据边界值计算的,其他用动态推导公式推导。用这个表可以看出所有的值都可以通过边界值公式和动态推导公式获得。
表后为c的推导程序,程序中只用了第一个边界值dp(0,0)=1,其他都用推导的方式获得。
j=0 | j=1 | j=2 | j=3 | j=4 | j=5 | j=6 | j=7 | j=8 | j=9 | j=10 | |
i=0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
i=1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
i=2 | 0 | 0 | 0 | 1 | dp(2,4)= dp(1,2)+dp(2,2) =1+0=1 | dp(2,5)= dp(1,3)+dp(2,3) =1+1=2 | dp(2,6)= dp(1,4)+dp(2,4) =1+1=2 | dp(2,7)= dp(1,5)+dp(2,5) =1+2=3 | dp(2,8)= dp(1,6)+dp(2,6) =1+2=3 | dp(2,9)= dp(1,7)+dp(2,7) =1+3=4 | dp(2,10) =dp(1,8)+dp(2,8) =1+3=4 |
i=3 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | dp(3,7)= dp(2,4)+dp(3,4) =1+0=1 | dp(3,8)= dp(2,5)+dp(3,5) =2+0=2 | dp(3,9)= dp(2,6)+dp(3,6) =2+1=3 | dp(3,10)= dp(2,7)+dp(3,7) =3+1=4 |
i=4 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
i=5 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
i=6 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
i=7 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
i=8 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
i=9 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
i=10 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
int main(){
int m=0;
int i=0, j=0;
printf("输入M:");
scanf("%d", &m);
int dp[30][30] = {0};
dp[0][0] = 1;
for (i=1; i<=m; i++)
{
for (j=i*(i+1)/2; j<=m; j++)
{
dp[i][j]=dp[i][j-i]+dp[i-1][j-i];
}
}
for (i=0; i<=m; i++)
{
printf("%d: ",i);
for (j=0; j<=m; j++)
{
printf("%d, ", dp[i][j]);
}
printf("\n");
}
int ans = 0;
for (i=1;i<=m;i++)
{
ans+=dp[i][m];
}
printf("answer : %d\n", ans);
}