HDU 1028
题目大意:
给一个数n,求出这个数的不重复整数排列。
解题思路:
(1)递归求解:栈深度有点高,可以通过记忆+剪枝优化。
(2)初始化二维数组dp,dp[i][j],表示当前输入的数字为i时,排列的第一个数字为j时有几种排列方法。
不难看出
1可以拆分成1+0,只有1种
2可以拆分成2+0,1+1两种。
3可以拆分成3+0,2+1,1+1+1,三种
4可以拆分成4+0,3+1,2+2,2+1+1,1+1+1+1,五种。
。。。。。。
以n=4为例,
4 = 4 + 0
4 = 3 + 1
4 = 2 + 2
4 = 2 +1 + 1
4 = 1 + 1 + 1 + 1
从上面可以看出,我们把第一个数字和后面的所有数字分开看,第一个数字最大,第二个其次,第三个依次和前面一样,相等或减小。一直到1 或 0 为止。这样递归就很明显了,这里用第二种方法求解,我们设dp数组,它的意义和上面说的一样。
(1)首先初始化dp数组。当第一个数字是1的时候,后面肯定都是1.这样dp[i][1] = 1;就确定了。
当第一个数字是n时,那么n = n + 0,也只有1种,所以dp[i][i] = 1也是确定的,同理,当 n = n-1时,后面那个1不可再分,也只能是1中,
这样初始化工作完成。
(2)初始化完成之后,我们分析一下规律。
我们先把n分成两个数字a和b,a从n到1递减,b=n-a,那么就有两种情况:
1.a >= b
2 a < b
情况1:当a >= b 时,我们直接计算sums(b)即可,这个sums(b,b)的值就是当总和为n的时候,第一个数字是a的排列种数。sums(x,y)函数定义:当n= x时,第一个数字从1到y的所有可能种类数的和。也就是从dp[x][1]一直加到dp[x][y],设立这个函数是必要性可以看情况2.
情况2:当 a < b 时, 就是说第一个数字小于第二个数字,那么如何让这样的情况也符合条件呢,那么就让第二个数字b拆分,拆分成a和b-a两个数字,这样第2个数字就小于等于第一个数字了,比如说:5 = 2 + 3 = 2 + 2 + 1,这样我们调用sums函数传参为sums(3,2),也就是说计算总和n=3,但是第一个拆分数字小于等于2的情况一共有多少种。也就是说对于这个n=5的例子中,以2为第一个数字的情况种数就等于3的以小于等于2为第一个数字的情况数之和。
综上所述,没输入一个数字n,就直接输出sums(n,n),就可以算出题目结果。测试AC代码如下
1 #include<cstdio> 2 #include<iostream> 3 #include<cstring> 4 #include<string> 5 using namespace std; 6 const int N = 124; 7 int dp[N][N]; 8 9 int res = 0; 10 11 int sums(int b,int a){ 12 int res = 0 ; 13 for(int i = 1 ; i<= a; ++i){ 14 res += dp[b][i]; 15 } 16 return res; 17 } 18 void init(){ 19 memset(dp,0,sizeof(dp)); 20 for(int i = 0 ;i < N ; ++i){ 21 dp[i][1] = 1; 22 dp[i][i] = 1; 23 dp[i][i-1] = 1; 24 } 25 for(int i = 3 ;i < 121; ++i){ 26 for(int j = 2; j < i - 1; ++j){ 27 int n = i , a = j, b = i - j; 28 if(a >= b){ 29 dp[i][j] = sums(b,b); 30 }else{ 31 dp[i][j] += sums(b,a); 32 } 33 } 34 } 35 } 36 37 int main(){ 38 int n; 39 init(); 40 while(cin>>n){ 41 cout<<sums(n,n)<<endl; 42 } 43 return 0 ; 44 }