1. 题目描述
- 题目描述
一个整数总可以拆分为2的幂的和,例如: 7=1+2+4 7=1+2+2+2 7=1+1+1+4 7=1+1+1+2+2 7=1+1+1+1+1+2 7=1+1+1+1+1+1+1 总共有六种不同的拆分方式。 再比如:4可以拆分成:4 = 4,4 = 1 + 1 + 1 + 1,4 = 2 + 2,4=1+1+2。 用f(n)表示n的不同拆分的种数,例如f(7)=6. 要求编写程序,读入n(不超过1000000),输出f(n)%1000000000。
输入描述 每组输入包括一个整数:N(1<=N<=1000000)。
输出描述 对于每组数据,输出f(n)%1000000000。
2. 初步分析与实现
2.1 递推式
这道题其实就是以前讲过的整数划分问题的一个变种。这道题的递推公式就是稍微变了一下而已,我们令 \(f(n, m)\) 表示用最大为 m 的数来组成 n,则有公式:
\[ \begin{cases} &f(n, m) = f(n, \frac{m}{2}) + f(n - m, m)\\ &m = 2^i \end{cases} \]
简单地分析一下初始条件,可以得到初版的代码。
1 | int resolve(int n, int m) |
纯递归的方式肯定会超时,需要变成动态规化的形式。
2.2 DP初版
这个式子直接变成动态规划的形式有点麻烦,因为 \(m\) 需要为 2 的阶乘,这就意味着如果我们令 \(dp[n][m] = f(n, m)\),那么二维数组 dp
中会有一大部分空间被浪费,所以我们做一点空间压缩,令:
\[ dp[n][2^j] = f(n, m) \]
1 | int C = 1000000000; |
3. 使用滚动数组
那么这道题就这么结了吗?并非如此,如果用上面的代码跑一下会发现结果是内存超限了。原因在于即使压缩了空间,这个二维数组还是太大了。所以需要进一步压缩空间,这里就需要用到名为滚动数组的技巧了。
3.1 滚动数组介绍
滚动数组的作用在于优化空间,主要应用在递推或动态规划中(如01背包问题)。因为DP题目是一个自底向上的扩展过程,我们常常需要用到的是连续的解,前面的解往往可以舍去。所以用滚动数组优化是很有效的。利用滚动数组的话在N很大的情况下可以达到压缩存储的作用。
举一个简单的例子,斐波那契数列。
1 | int Fib[25]; |
我们发现实际上要用到的空间远远没有 n 个,利用滚动数组优化后代码为:
1 | int Fib[3]; |
这个“滚动”可以说是很形象了。
3.2 使用滚动数组优化
要使用滚动数组优化空间,我们就需要搞清楚哪些空间是用得到的,哪些是用不到的,仔细观察状态转移式子:
1 | a = dp[n][j - 1] % C; |
我们发现,在写入 dp[n][j]
,时需要的老信息只有两个:
- 同为
j
列的数据 dp[n][j - 1]
也就是说 dp[:][0:j-1]
和 dp[1:n][j-1]
的数据都是不用管的老数据。我们可以只保留 2 列数据,并且在迭代中不断把这两行数据往右“滚动”。
1 | int dp[N][2]; |