Ural1017(DP)
来源:互联网 发布:如何建立网络连接 编辑:程序博客网 时间:2024/04/30 14:36
转自:http://www.cnblogs.com/skyivben/archive/2009/03/02/1401728.html
1017. The Staircases
Memory Limit: 16 MB
Input
Output
Sample
题意
这道题目要求计算给定数目的砖块可以组成多少种不同的楼梯。楼梯由不同高度的阶梯组成,不允许有两个阶梯有相同的高度。每个楼梯至少包含两个阶梯,每个阶梯至少包含一个砖块。
数学背景
这道题目涉及到数的分划,属于组合数学和数论的研究领域。
数 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),其部分值见下表:
最后,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),其部分值见下表:
最后,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年首先证明的。
附上另外一种理解此题的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);}
- Ural1017(DP)
- ural1017
- Ural1017
- ural1017 Staircases (动态规划)
- dp
- dp
- dp
- 【DP】
- dp
- dp
- DP
- DP
- DP
- DP
- DP
- dp
- DP
- dp
- Mac OS X:sudo 命令需要非空的管理员密码
- linux安装网卡驱动
- c++11
- php配置yii框架
- Linux下查看主机支持的内存
- Ural1017(DP)
- 微信公众平台的通讯过程
- 印度央行担心比特币信誉,但是没有叫停它(来自于http://techcrunch.com/)
- 最新MySQL安装配置教程
- set_f1于clr_f1
- SlidingMenu的调用方法
- 从沙子到芯片:且看处理器是怎样炼成的
- 无法挂载 NTFS格式的分区:mount: unknown filesystem type ‘ntfs’。
- ODOA(1) 翻转句子中单词的顺序(C语言实现)