Ural1017(DP)

来源:互联网 发布:如何建立网络连接 编辑:程序博客网 时间:2024/04/30 14:36

转自:http://www.cnblogs.com/skyivben/archive/2009/03/02/1401728.html

1017. The Staircases

Time Limit: 1.0 second
Memory Limit: 16 MB
One curious child has a set of N little bricks (5 ≤ N ≤ 500). From these bricks he builds different staircases. Staircase consists of steps of different sizes in a strictly descending order. It is not allowed for staircase to have steps equal sizes. Every staircase consists of at least two steps and each step contains at least one brick. Picture gives examples of staircase for N=11 and N=5:
Problem illustration
Your task is to write a program that reads from input file one number N and writes to output file the only number Q — amount of different staircases that can be built from exactly N bricks.

Input

Number N

Output

Number Q

Sample

inputoutput212995645335Problem Source: Ural State University Internal Contest '99 #2

题意

这道题目要求计算给定数目的砖块可以组成多少种不同的楼梯。楼梯由不同高度的阶梯组成,不允许有两个阶梯有相同的高度。每个楼梯至少包含两个阶梯,每个阶梯至少包含一个砖块。

数学背景

这道题目涉及到数的分划,属于组合数学和数论的研究领域。

数 n 的分划(partition)是将 n 表示成任意多个正整数之和的形式。例如,数 5 的分划如下:

  1. 5
  2. 4 + 1
  3. 3 + 2
  4. 3 + 1 + 1
  5. 2 + 2 + 1
  6. 2 + 1 + 1 + 1
  7. 1 + 1 + 1 + 1 + 1

用 p(n) 来记 n 的分划的个数,这样就有 p(5) = 7。

为了求解 p(n),我们引入一个中间函数 p(k, n),表示数 n 的最小被加数不小于 k 的分划的个数。对于给定的 k 值,p(k, n) 正好分为以下两类:

  1. 最小被加数等于 k
  2. 最小被加数大于 k

满足第一个条件的分划的个数是 p(kn − k)。 这是因为,让我们想象数 n − k 的最小被加数不小于 k 的分划,然后将 "+ k" 附加每一个分划后面,就得到数 n 的最小被加数等于 k 的分划。以 n = 5, k = 1 为例,数 4 的最小被加数不小于 1 的分划是 43 + 12 + 22 + 1 + 1 和 1 + 1 + 1 + 1,即 p(kn − k) = p(1, 4) = 5。然后,将 "+ 1" 附加在这 5 个分划后面,就得到数 5 的最小被加数等于 1 的分划:4 + 13 + 1 + 12 + 2 + 12 + 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(kn) = 0  如果 k > n
  • p(kn) = 1  如果 k = n
  • p(kn) = p(k+1, n) + p(kn-k)  其它情况

这样,就可以递归地求解 p(k, n),其部分值见下表:

 k12345678910n110000000002210000000033110000000452110000005721110000061142111000071542111100082273211110093084211111010421253211111

最后,p(n) = p(1, n)

 


现在,让我们的来考虑 将 n 分成不相等的正整数之和的分划。例如,数 8 的分划如下:

  1. 8
  2. 7 + 1
  3. 6 + 2
  4. 5 + 3
  5. 5 + 2 + 1
  6. 4 + 3 + 1

用 q(n) 来记 n 的分划的个数,这样就有 q(8) = 6。

为了求解 q(n),我们引入一个中间函数 q(k, n),表示数 n 的最小被加数不小于 k 的分划的个数。对于给定的 k 值,q(k, n) 正好分为以下两类:

  1. 最小被加数等于 k
  2. 最小被加数大于 k

满足第一个条件的分划的个数是 q(k + 1, n − k)。 这是因为,让我们想象数 n − k 的最小被加数大于 k 的分划,然后将 "+ k" 附加每一个分划后面,就得到数 n 的最小被加数等于 k 的分划。以 n = 8, k = 1 为例,数 7 的最小被加数大于 1 的分划是 75 + 2 和 4 + 3,即 q(k + 1, n − k) = q(2, 7) = 3。然后,将 "+ 1" 附加在这 3 个分划后面,就得到数 8 的最小被加数等于 1 的分划:7 + 15 + 2 + 1 和 4 + 3 + 1

满足第二个条件的分划的个数是 q(k + 1, n) 。以 n = 8, k = 1 为例,数 8 的最小被加数大于 1 的分划是 86 + 2 和 5 + 3,即 q(k + 1, n) = q(2, 8) = 3。

也就是说,q(1, 8) = q(2, 8) + q(2, 7)。因此:

  • q(kn) = 0  如果 k > n
  • q(kn) = 1  如果 k = n
  • q(kn) = q(k+1, n) + q(k + 1, n-k)  其它情况

这样,就可以递归地求解 q(k, n),其部分值见下表:

 k12345678910n1100000000021100000000321100000004211100000053211100000642111100007532111100086321111100985321111101010532111111

最后,q(n) = q(1, n)

解题思路

用 n 块砖块可以组成的楼梯的个数,就是将 n 分成不相等的正整数之和的分划的个数 q(n) 减一,因为每个楼梯至少包含两个阶梯。

C++ 程序:

[cpp] view plaincopy
  1. #include <algorithm>  
  2. #include <iostream>  
  3. #include <cstring>  
  4. #include <string>  
  5. #include <cstdio>  
  6. #include <vector>  
  7. #include <queue>  
  8. #include <cmath>  
  9. #include <map>  
  10. #include <set>  
  11. #define LL long long  
  12. #define pb push_back  
  13. #define Max(a,b) ((a)>(b)?(a):(b))  
  14. #define Min(a,b) ((a)<(b)?(a):(b))  
  15. using namespace std;  
  16.   
  17. const int inf=0x3f3f3f3f;  
  18. const int maxn=550;  
  19. LL dp[maxn][maxn];  
  20.   
  21. int main()  
  22. {  
  23.     LL n;  
  24.     scanf("%I64d",&n);  
  25.     memset(dp,0,sizeof(dp));  
  26.     for(int i=1;i<=n;i++) dp[i][i]=1;  
  27.     for(int i=1;i<=n;i++){  
  28.         for(int j=i-1;j>=1;j--){  
  29.             dp[i][j]=dp[i][j+1]+dp[i-j][j+1];  
  30.         }  
  31.     }  
  32.     printf("%I64d\n",dp[n][1]-1);  
  33.     return 0;  
  34. }  


相应的 C# 程序如下: 

复制代码
 1 namespace Skyiv.Ben.Timus
 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,则得到以下等价的程序:

复制代码
 1 namespace Skyiv.Ben.Timus
 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年首先证明的。




附上另外一种理解此题的DP思想。

DP[i][j]表示总共有i个砖块,搭成j阶台阶的情况数。

因为可以搭成j阶台阶,所以最下面一层一定是j个砖块,把j个砖块拿走,则可以化出2种子状态。

一种是拿走后依然剩下j阶台阶的,另一种是拿走后剩下j-1阶台阶的。

则可列出状态转移方程DP[i][j]=DP[i-j][j]+DP[i-j][j-1]。

注意将DP[i][1]都置为1。

#include <cstdio>#include <cstring>#include <iostream>#include <queue>using namespace std;int main(){int n;scanf("%d",&n);long long dp[505][105]={{0}};int i,j;dp[3][2]=1;dp[4][2]=1;for (i=1;i<=n;i++)dp[i][1]=1;for (i=5;i<=n;i++){for (j=2;(1+j)*j/2<=i;j++)if (i>j)dp[i][j]=dp[i-j][j]+dp[i-j][j-1];}long long sum=0;for (i=2;(1+i)*i/2<=n;i++)sum+=dp[n][i];printf("%I64d\n",sum);}


0 0
原创粉丝点击