P1140 数的划分
原题点这里
思路
这是一道动态规划的题目。
步骤主要分 5 5 5 步:
- 状态的定义
- 转移式的推到
- 递推顺序的判定
- 边界的确定
- 结果的输出
下面,我们针对这道题,细细地讲解一下每一个步骤
一、状态的定义
这道题的状态最简单,又直白——就是
f
i
,
j
f_{i, j}
fi,j 表示数字
i
i
i,划分成
j
j
j 个部分的方案数。
对于状态的定义大家可以多去试一试,不行了就换一种思路,做多了就会有思路,一般的定义所具备的都是至少有一个
f
i
f_i
fi 代表是第
i
i
i 个。
二、转移式的推到
一般情况下,如果状态找对了,那么转移式便会呼之欲出了。
这道题比较难想,运用到分类讨论的思想!
情况1:
如果分出的数中,有一个 1 1 1,那么 f i , j f_{i, j} fi,j 的方案数其实就与 f i − 1 , j − 1 f_{i - 1, j - 1} fi−1,j−1 的方案数相同。
可以这样考虑:如果有一个 1 1 1,去掉这个 1 1 1 之后就会有 j − 1 j - 1 j−1 个部分,总和也会因 − 1 -1 −1 而跟随着 − 1 -1 −1,故总和为 i − 1 i - 1 i−1,此时少了个 1 1 1 不会影响其他数的方案数
情况2:
如果分出的数中,没有 1 1 1,那么 f i , j f_{i, j} fi,j 的方案数其实就与 f i − j , j f_{i - j, j} fi−j,j 相同。
可以这样考虑:如果有一个 1 1 1,每一个部分减掉这个 1 1 1 之后还是 j j j 个部分,总和也会因每个部分 − 1 -1 −1 而跟随着 − j -j −j,故总和为 i − j i - j i−j,此时每一组少了个 1 1 1 不会影响方案数
综上所述:
f
i
,
j
=
f
i
−
1
,
j
−
1
+
f
i
−
j
,
j
\large f_{i, j} = f_{i - 1, j - 1} + f_{i - j, j}
fi,j=fi−1,j−1+fi−j,j
三、递推顺序的判定
这一部分就很简单了,对于这道题因为我们发现一定会要么相同,要么由前面的状态所转移。
为了保持无后效性,我们需采用由小到大的顺序枚举
四、边界的确定
这道题大家可以想一想,如果一个数只划分成 1 1 1 份,那么方案数只有 1 1 1 种。
所以,这就是我们初始化的内容:将 f i , 1 f_{i, 1} fi,1 全部初始化为 1 1 1。
一般情况下,边界都是初始化最初用到的值。
五、结果的输出
根据我们的状态的定义,不难确定出答案就是 f n , k f_{n, k} fn,k( n , k n, k n,k就是题目中所说的)
香喷喷的代码来了
#include <iostream>
using namespace std;
const int N = 1e3 + 10;
int n, k;
int f[N][N];
int main()
{
cin >> n >> k;
for (int i = 1; i <= n; i ++) f[i][1] = 1;
for (int i = 2; i <= n; i ++)
for (int j = 2; j <= min(i, k); j ++)
f[i][j] = f[i - 1][j - 1] + f[i - j][j];
cout << f[n][k] << endl;
}
最后祝大家早日——
烈火熊熊灼尔等,宁为玉碎不瓦全