[动态规划] 整数分解加数

 
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;
 
问题:给定任意一个正整数N,能够找到多少个这种等式。
以4为例。
  4 = 4;
  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)也写出递归式,否则无法自底向上计算。
posted @ 2012-08-31 10:25  苦力笨笨  阅读(3523)  评论(0编辑  收藏  举报