[动态规划] 整数分解加数
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)
Problem Description
"Well, it seems the first problem is too easy. I will let you know how foolish you are later." feng5166 says.
"The second problem is, given an positive integer N, we define an equation like this:
N=a[1]+a[2]+a[3]+...+a[m];
a[i]>0,1<=m<=N;
My question is how many different equations you can find for a given N.
For example, assume N is 4, we can find:
4 = 4;
4 = 3 + 1;
4 = 2 + 2;
4 = 2 + 1 + 1;
4 = 1 + 1 + 1 + 1;
so the result is 5 when N is 4. Note that "4 = 3 + 1" and "4 = 1 + 3" is the same in this problem. Now, you do it!"
Input
The input contains several test cases. Each test case contains a positive integer N(1<=N<=120) which is mentioned above. The input is terminated by the end of file.
Output
For each test case, you have to output a line contains an integer P which indicates the different equations you have found.
Sample Input
4
10
20
Sample Output
5
42
627
问题翻译:
给定一个正整数N,我们可以定义一个等式。
N=a[1]+a[2]+a[3]+...+a[m];
a[i]>0,1<=m<=N;
a[i]>0,1<=m<=N;
问题:给定任意一个正整数N,能够找到多少个这种等式。
以4为例。
4 = 4;
4 = 3 + 1;
4 = 2 + 2;
4 = 2 + 1 + 1;
4 = 1 + 1 + 1 + 1;
4 = 3 + 1;
4 = 2 + 2;
4 = 2 + 1 + 1;
4 = 1 + 1 + 1 + 1;
共有5个等式,注意"4=3+1"与"4=1+3"被定义为相同的等式。
输入:
每行一个正整数N(1<=N<=120),可能包含多行。
输出:
每行一个结果,表示可分解的等式个数。
输入示例:
4
10
20
输出示例:
5
42
627
由于"4=1+3"和"4=3+1"是相同的等式,不妨约定等式中加数按递减顺序出现。
手工分析5这个数,可以写成以下几个等式。
5=5
5=4+1
5=3+2
5=3+1+1
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1
通过观察,可以发现这些等式可以按最大加数,分为几组。如最大加数为4的等式只有一个"5=4+1",最大加数为3的等式有2个,最大加数为2的等式也有2个。因此考虑将问题按最大加数的大小,分解为几个子问题。
当最大加数为3时,所有的等式数量,相当于对2(5-3=2)分解加数。
但是当最大加数为2时,所有等式的数量,不能按对3分解加数的值来计算。因为对3分解加数,最大加数可能大于2,这与之前约定的递减顺序冲突,会造成重复计算。例如会重复计算"5=2+3",这个等式在最大加数为3时已经计算过。所以,当最大加数为2时,子问题的解应该是对3分解加数,且最大加数不超过2的等式的数量。
为了描述这个值,定义函数f(x,y)为对x分解加数,且最大加数不超过y的等式的数量。原问题的解是f(N,N),其中N为问题的输入。
至此,可以写出递归公式。
f(x,y) = ∑(i from 1 to y)( f(x-i, i) )
当y>x时,f(x,y) = f(x,x)。从等式原始意义不难得出这个结论。
其中,f(x,1) = 1,最大加数为1的等式只有一个,即x = 1 + 1 + 1 + ... + 1。为了形式上处理x = x + 0的等式,定义f(0, y) = 1。f(x,0)无意义。
可以利用动态规划求解。以7为例模拟求解全过程为。表格横坐标代表f(x,y)中的x,纵坐标代表y。
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | |
1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
2 | 1 | 2 | 2 | 3 | 3 | 4 | 4 | |
3 | 1 | 3 | 4 | 5 | 7 | 8 | ||
4 | 1 | 5 | 6 | 9 | 11 | |||
5 | 1 | 7 | 10 | 13 | ||||
6 | 1 | 11 | 14 | |||||
7 | 1 | 15 |
以f(7,7)为例(蓝色表格框),其值相当于对绿色框内所有数字求和。随着参数y从1开始增至x,绿色框从右上方向左下方延伸。
代码示例:
代码仅简单写出算法,并未完全按照题目的输入输出处理。代码输入的输入为N,输出为等式个数。
如:
> python my_alg.py 7
15
1 #!/usr/local/bin/python2.6 2 3 import sys 4 5 g_table = [] 6 7 def Init(num): 8 global g_table 9 for i in range(0, num + 1): 10 lst_num = [] 11 for i in range(0, num + 1): 12 lst_num.append(-1) 13 g_table.append(lst_num) 14 for i in range(0, num + 1): 15 g_table[i][1] = 1 16 g_table[i][0] = 1 17 18 def GetNumber(n, x): 19 global g_table 20 if x > n: 21 return g_table[n][n] 22 return g_table[n][x] 23 24 def PutNumber(n, x, num): 25 global g_table 26 g_table[n][x] = num 27 28 def Compute(n, x): 29 sum = 0 30 for i in range(1, x + 1): 31 sum += GetNumber(n - i, i) 32 return sum 33 34 def Cal(n): 35 for i in range(2, n + 1): 36 for j in range(2, i + 1): 37 num = Compute(i, j) 38 PutNumber(i, j, num) 39 return GetNumber(n, n) 40 41 def main(): 42 n = int(sys.argv[1]) 43 Init(n) 44 print Cal(n) 45 46 if __name__ == '__main__': 47 main()
以前在算法课上学习动态规划,一知半解。后来自己读《算法导论》,也看了wiki上对与动态规划的介绍。动态规划的理论包括Bellman等式,还有状态转移函数等。记得老师在课堂上说用动态规划处理问题,要先写出状态转移函数。但是思考过一些问题,总感觉一开始就找状态转移函数,不太容易写出来。比如本问题、矩阵连乘问题、以及硬币找零问题的状态转义函数,该如何写呢。这几个问题并没有明显的stage顺序。
反倒是感觉,应该先分解问题,寻找子问题,寻找递归式。动态规划的本质是记录子问题的结果,避免重复计算子问题。如果能找到问题的递归关系,就可以自底向上地计算。
在实际中,问题的解可能并不直接依赖于相同形式的子问题。
比如这道题,若定义f(N)为N分解加数的等式个数,则f(N)并不依赖于规模更小的f(N-1),f(N-2),....,f(3)等,而是依赖于有限制条件的等式个数,因此定义f(x, y)为最大加数为y的等式个数。这样f(N, N)就代表了不加限制的等式个数。
再比如“能量苹果”那个问题,f(Lx)也不是只依赖于相同形式规模更小的子问题f(Lx-1),f(Lx-2),...,f(L3)等,还依赖于吃了偶数个苹果的情况。因此定义g(Lx)为吃了偶数个苹果时的最大能量,这时必须为g(Lx)也写出递归式,否则无法自底向上计算。