转自:http://www.cnblogs.com/skyivben/archive/2009/03/02/1401728.html
1017. The Staircases
Memory Limit: 16 MB
![Problem illustration](https://i-blog.csdnimg.cn/blog_migrate/9d85bda6d7c11fb2d9ab7b7abfb1f378.png)
Input
Output
Sample
input | output |
---|---|
212 | 995645335 |
题意
这道题目要求计算给定数目的砖块可以组成多少种不同的楼梯。楼梯由不同高度的阶梯组成,不允许有两个阶梯有相同的高度。每个楼梯至少包含两个阶梯,每个阶梯至少包含一个砖块。
数学背景
数 n 的分划(partition)是将 n 表示成任意多个正整数之和的形式。例如,数 5 的分划如下:
- 5
- 4 + 1
- 3 + 2
- 3 + 1 + 1
- 2 + 2 + 1
- 2 + 1 + 1 + 1
- 1 + 1 + 1 + 1 + 1
用 p(n) 来记 n 的分划的个数,这样就有 p(5) = 7。
为了求解 p(n),我们引入一个中间函数 p(k, n),表示数 n 的最小被加数不小于 k 的分划的个数。对于给定的 k 值,p(k, n) 正好分为以下两类:
- 最小被加数等于 k
- 最小被加数大于 k
满足第一个条件的分划的个数是 p(k, n − k)。 这是因为,让我们想象数 n − k 的最小被加数不小于 k 的分划,然后将 "+ k" 附加每一个分划后面,就得到数 n 的最小被加数等于 k 的分划。以 n = 5, k = 1 为例,数 4 的最小被加数不小于 1 的分划是 4、3 + 1、2 + 2、2 + 1 + 1 和 1 + 1 + 1 + 1,即 p(k, n − k) = p(1, 4) = 5。然后,将 "+ 1" 附加在这 5 个分划后面,就得到数 5 的最小被加数等于 1 的分划:4 + 1、3 + 1 + 1、2 + 2 + 1、2 + 1 + 1 + 1 和 1 + 1 + 1 + 1 + 1。
满足第二个条件的分划的个数是 p(k + 1, n) 。以 n = 5, k = 1 为例,数 5 的最小被加数大于 1 的分划是 5 和 3 + 2,即 p(k + 1, n) = p(2, 5) = 2。
也就是说,p(1, 5) = p(2, 5) + p(1, 4)。因此:
- p(k, n) = 0 如果 k > n
- p(k, n) = 1 如果 k = n
- p(k, n) = p(k+1, n) + p(k, n-k) 其它情况
这样,就可以递归地求解 p(k, n),其部分值见下表:
k | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | ||
n | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
2 | 2 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | |
3 | 3 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | |
4 | 5 | 2 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | |
5 | 7 | 2 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | |
6 | 11 | 4 | 2 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | |
7 | 15 | 4 | 2 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | |
8 | 22 | 7 | 3 | 2 | 1 | 1 | 1 | 1 | 0 | 0 | |
9 | 30 | 8 | 4 | 2 | 1 | 1 | 1 | 1 | 1 | 0 | |
10 | 42 | 12 | 5 | 3 | 2 | 1 | 1 | 1 | 1 | 1 |
最后,p(n) = p(1, n)。
现在,让我们的来考虑 将 n 分成不相等的正整数之和的分划。例如,数 8 的分划如下:
- 8
- 7 + 1
- 6 + 2
- 5 + 3
- 5 + 2 + 1
- 4 + 3 + 1
用 q(n) 来记 n 的分划的个数,这样就有 q(8) = 6。
为了求解 q(n),我们引入一个中间函数 q(k, n),表示数 n 的最小被加数不小于 k 的分划的个数。对于给定的 k 值,q(k, n) 正好分为以下两类:
- 最小被加数等于 k
- 最小被加数大于 k
满足第一个条件的分划的个数是 q(k + 1, n − k)。 这是因为,让我们想象数 n − k 的最小被加数大于 k 的分划,然后将 "+ k" 附加每一个分划后面,就得到数 n 的最小被加数等于 k 的分划。以 n = 8, k = 1 为例,数 7 的最小被加数大于 1 的分划是 7、5 + 2 和 4 + 3,即 q(k + 1, n − k) = q(2, 7) = 3。然后,将 "+ 1" 附加在这 3 个分划后面,就得到数 8 的最小被加数等于 1 的分划:7 + 1、5 + 2 + 1 和 4 + 3 + 1。
满足第二个条件的分划的个数是 q(k + 1, n) 。以 n = 8, k = 1 为例,数 8 的最小被加数大于 1 的分划是 8、6 + 2 和 5 + 3,即 q(k + 1, n) = q(2, 8) = 3。
也就是说,q(1, 8) = q(2, 8) + q(2, 7)。因此:
- q(k, n) = 0 如果 k > n
- q(k, n) = 1 如果 k = n
- q(k, n) = q(k+1, n) + q(k + 1, n-k) 其它情况
这样,就可以递归地求解 q(k, n),其部分值见下表:
k | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | ||
n | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
2 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | |
3 | 2 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | |
4 | 2 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | |
5 | 3 | 2 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | |
6 | 4 | 2 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | |
7 | 5 | 3 | 2 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | |
8 | 6 | 3 | 2 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | |
9 | 8 | 5 | 3 | 2 | 1 | 1 | 1 | 1 | 1 | 0 | |
10 | 10 | 5 | 3 | 2 | 1 | 1 | 1 | 1 | 1 | 1 |
最后,q(n) = q(1, n)。
解题思路
用 n 块砖块可以组成的楼梯的个数,就是将 n 分成不相等的正整数之和的分划的个数 q(n) 减一,因为每个楼梯至少包含两个阶梯。
C++ 程序:
#include <algorithm>
#include <iostream>
#include <cstring>
#include <string>
#include <cstdio>
#include <vector>
#include <queue>
#include <cmath>
#include <map>
#include <set>
#define LL long long
#define pb push_back
#define Max(a,b) ((a)>(b)?(a):(b))
#define Min(a,b) ((a)<(b)?(a):(b))
using namespace std;
const int inf=0x3f3f3f3f;
const int maxn=550;
LL dp[maxn][maxn];
int main()
{
LL n;
scanf("%I64d",&n);
memset(dp,0,sizeof(dp));
for(int i=1;i<=n;i++) dp[i][i]=1;
for(int i=1;i<=n;i++){
for(int j=i-1;j>=1;j--){
dp[i][j]=dp[i][j+1]+dp[i-j][j+1];
}
}
printf("%I64d\n",dp[n][1]-1);
return 0;
}
相应的 C# 程序如下:
2 {
3 // http://acm.timus.ru/problem.aspx?space=1 &num=1017
4 sealed class T1017
5 {
6 static void Main()
7 {
8 var m = int .Parse(System.Console.ReadLine());
9 var q = new long [m + 1 , m + 1 ];
10 for (var k = 1 ; k <= m; k ++ ) q[k, k] = 1 ;
11 for (var n = 2 ; n <= m; n ++ )
12 for (var k = n - 1 ; k >= 1 ; k -- )
13 q[k, n] = q[k + 1 , n] + q[k + 1 , n - k];
14 System.Console.WriteLine(q[ 1 , m] - 1 );
15 }
16 }
17 }
上述程序中的二维数组 q 的下标 0 没有使用,因此浪费了 (2m + 1) * sizeof(long) 字节的空间。因此将 k 和 n 的取值范围从 1 到 m 修改为 0 到 m-1,则得到以下等价的程序:
2 {
3 // http://acm.timus.ru/problem.aspx?space=1 &num=1017
4 sealed class T1017
5 {
6 static void Main()
7 {
8 var m = int .Parse(System.Console.ReadLine());
9 var q = new long [m, m];
10 for (var k = 0 ; k < m; k ++ ) q[k, k] = 1 ;
11 for (var n = 1 ; n < m; n ++ )
12 for (var k = n - 1 ; k >= 0 ; k -- )
13 q[k, n] = q[k + 1 , n] + q[k + 1 , n - k - 1 ];
14 System.Console.WriteLine(q[ 0 , m - 1 ] - 1 );
15 }
16 }
17 }
注意第 13 行的公式中最后一个下标从 n - k 变为 n - k - 1。这是因为该公式位于 n 和 k 的二重循环中,这两个循环变量的取值范围都已经减一了,n - k 的值抵消了减一,所以也需要再减一。
进一步的阅读
首先,推荐维基百科网站的如下页面:Partition (number theory)。
组合数学(原书第4版),(美) Richard A.Brualdi 著,冯舜玺等译,机械工业出版社,2005年2月第1版。该书第八章“特殊计数序列”,第三节“8.3 分拆数”。
数论导引(原书第5版),(英) G.H.Hardy, E.M.Wright 著,张明尧等译,人民邮电出版社,2008年10月第1版。该书第十九章“分划”。
关于数的分划,有许多有趣的定理,如:
定理 344 将 n 分成若干个不相等的数之和的分划个数等于将它分成若干个奇数之和的分划个数。
这个定理是伟大的数学家欧拉(Leonard Euler)在1748年首先证明的。