经典dp,整数划分。
套路的想法是把复杂的问题划分为多个1相加。
对于这道题考虑到如果划分出来的某个数是1,那么去掉这个1,这个数就没了。
所以把问题拆分之后,划分出一个新的数的条件就是多分出一个一。
计数问题,想要计所有情况的数一般不难,难的是去重。
这道问题我们现在只需要考虑取了哪些值,而不能多关心其它的,否则就会有重复状态。
在转移的时候,我们显然不需要也不能关心之前划分具体是怎么样的:
我们只需要考虑之前用掉了多少值,划分出了几个数就可以了。
状态就有了。
(
i
,
j
)
\mathcal{(i,j)}
(i,j)。
按照拆分问题的思路,一个新的数出现,那么它的初始值应该是一。
至于要让现在这些数的值增加,我们显然不好“从里面找一个”加一。
因为这样的话就是关心划分的具体情况了。这不好。
所以我们就得把前面的看作整体,全部加上一。
随着前面这些数出现时间可重集的不同,我们刚好能够统计出
(
i
,
j
)
\mathcal{(i,j)}
(i,j)时的划分方案数
F
(
i
,
j
)
\mathcal{F(i,j)}
F(i,j)
实现的时候用填表法或者刷表法都不会重复计数。
填表
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cmath>
#include<cctype>
#include<cstring>
using namespace std;
int T, n, m;
int F[105][105];
int main() {
scanf("%d", &T);
while (T--) {
memset(F, 0, sizeof(F));
scanf("%d%d", &m, &n);
F[0][0] = 1;
for (int i = 1; i <= m; ++i) {
for (int j = 1; j <= i; ++j) {
F[i][j] = F[i-1][j-1] + F[i-j][j];
}
}
printf("%d\n", F[m][n]);
}
return 0;
}
刷表
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cmath>
#include<cctype>
#include<cstring>
using namespace std;
int T, n, m;
int F[105][105];
int main() {
scanf("%d", &T);
while (T--) {
memset(F, 0, sizeof(F));
scanf("%d%d", &m, &n);
F[0][0] = 1;
for (int i = 0; i <= m; ++i) {
for (int j = 0; j <= i; ++j) {
if (i+1 <= m && j+1 <= n) F[i+1][j+1] += F[i][j];
if (i+j <= m) F[i+j][j] += F[i][j];
}
}
printf("%d\n", F[m][n]);
}
return 0;
}