第一行:将n划分成若干个正整数之和的划分数
我们设dp[i][j] 表示将正整数i划分成最大数不超过数j的划分数,显然我们有:
(1):如果划分的数中至少有一个有j,则有:dp[i][j] = dp[i-j][j];
(2):如果划分的数中没有j,则有:dp[i][j] = dp[i][j-1];
因此有:dp[i][j] = dp[i-j][j] + dp[i][j-1];
借助此问可得出第三问的结果:第三行: 将n划分成最大数不超过k的划分数。dp[n][k]
第二行: 将n划分成k个正整数之和的划分数。
首先,正确理解“整数n分成k份,这k个整数不考虑顺序的含义”指的是同一种划分与k个整数的排列无关,例如下面5种8的4划分看作是同一种划分法:
1+2+2+3;2+2+3+1;1+3+2+2;3+2+1+2 ;2+2+1+3。
因此将n划分份的一种方法唯一的表示为n1+n2+……nk,其中n1<=n2<=….nk.
这样可以形象地把n的k份划分看作是把n块积木堆成k列,且每列的积木块数依次递增,也就是这n块积木从左到右被堆成了“阶梯状”。比如,下图是5的2种划分方法:
把上图的两个矩形顺时针旋转90度后,如下图:
第四行:将n划分成若干奇正整数之和的划分数。
dp1[i][j]是当前的划分数为i,最大值为j时的中的划分数,则状态转移方程为dp1[i][j] =
第五行: 将n划分成若干不同整数之和的划分数。
1:划分数中出现最大数j,则:dp2[i][j] = dp2[i-j][j-1]
2:划分数中没有出现最大数j,则:dp2[i][j] = dp2[i][j-1],因此:
dp2[i][j] = dp2[i][j-1] + dp2[i-j][j-1]
#include <iostream> #include <cstring> #include <cstdio> using namespace std; const int MAXN = 55; int dp[MAXN][MAXN]; int dp1[MAXN][MAXN]; int dp2[MAXN][MAXN]; void Pre_Solve1() { int i, j; for (i = 1; i < MAXN; ++i) { for(j = 1; j < MAXN; ++j) if(i < j) dp[i][j] = dp[i][i]; else dp[i][j] = dp[i][j-1] + dp[i-j][j]; } return ; } void Pre_Solve2() { int i, j; for (i = 1; i < MAXN; ++i) { for (j = 3; j < MAXN; j += 2) { if(i < j && i & 1) dp1[i][j] = dp1[i][i]; else if(i < j && !(i & 1)) dp1[i][j] = dp1[i][i-1]; else if(i >= j) dp1[i][j] = dp1[i-j][j] + dp1[i][j-2]; } } return ; } void Pre_Solve3() { int i, j; for (i = 1; i < MAXN; ++i) { for (j = 1; j < MAXN; ++j) { if(i < j) dp2[i][j] = dp2[i][i]; else dp2[i][j] = dp2[i-j][j-1] + dp2[i][j-1]; } } return ; } int main() { int i; int n, k; memset(dp, 0, sizeof(dp)); memset(dp1, 0, sizeof(dp1)); memset(dp2, 0, sizeof(dp2)); for (i = 0; i < MAXN; ++i) { dp[0][i] = 1; dp1[i][1] = 1; dp2[0][i] = 1; } for (i = 1; i < MAXN; i += 2) { dp1[0][i] = 1; } dp1[0][0] = 0; Pre_Solve1(); Pre_Solve2(); Pre_Solve3(); while (~scanf("%d %d", &n, &k)) { printf("%d\n%d\n%d\n%d\n%d\n\n", dp[n][n], dp[n-k][k], dp[n][k], (n&1) ? dp1[n][n] : dp1[n][n-1], dp2[n][n]); } return 0; }