鱼C论坛

 找回密码
 立即注册
查看: 3040|回复: 5

数的划分

[复制链接]
发表于 2022-9-25 16:22:53 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
题目 : https://www.luogu.com.cn/problem/P1025
不理解为什么状态转移方程是(i代表数字, j代表划分的个数, f[i][j] 代表数字 i 划分成 j 个数字的和一共的方法数
  1. if(i >= j) f[i][j] = f[i-1][j-1] + f[i-j][j];
复制代码

f[i-1][j-1] 我懂, 就是第一个是 1 , 后面剩下的数分 j-1 个位置
但是我自己算的时候感觉应该是 f[i-1][j-1]  + f[i-2][j-1] + ... + f[i-i/j][j-1]
这样才对 , 但是又有重复 , 求解为什么是上面的那个方程 , 怎么推的
小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

发表于 2022-9-25 17:18:14 | 显示全部楼层
你是要程序代码还是要运算什么的
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2022-9-25 17:32:03 | 显示全部楼层
编程追风梦 发表于 2022-9-25 17:18
你是要程序代码还是要运算什么的

就是解释这个方程是怎么推出的
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2022-9-25 18:10:21 | 显示全部楼层
本帖最后由 jhq999 于 2022-9-25 18:24 编辑

不懂什么状态转移方程
这道题我的思路

n m
n-m+1         1 1....1(m-1个1)
n-m-1+1      2 1.....1(m-2 g个1)
n-m-2+1      3 1....1,           n-m-2+1 2 2 1.....1
n-m-3 +1     4 1....1 ,            n-m-3+1 3 2 1......1,      n-m-3+1 2 2 2 1....1 //就是后面只能小于等于前面
......
n-m-i+1        j 1..........1(m-j个1)
结束条件:n-m-i+1<j

7 3
5 1 1
4 2 1
3 3 1    3 2 2
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2022-9-26 07:11:49 | 显示全部楼层
柿子饼同学 发表于 2022-9-25 17:32
就是解释这个方程是怎么推出的

好的,我试着解答一下
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2022-9-26 07:17:33 | 显示全部楼层
其实你可以这样去想,用动态规划来思考


[b]什么是动态规划?[/b]
在递归的时候,我们可以通过不断地分解问题,将复杂的任务简化为最基本的小问题,比如基于递归实现的归并排序,排列,组合等。不过有时候,我们并不用处理所有可能的情况,只要找到满足条件的最优解就可以了,这种情况下,我们需要在各种可能的局部解中,找出那些可能达到最优的局部解,而放弃其他的局部解,这个寻找最优解的过程叫动态规划。
[b]怎么判断一个问题是否可以由动态规划来解决?[/b]
首先,如果一个问题有多种可能,看上去需要排列或者组合的思想,但是最终求的只是最优解,如最大值,最小值,最短子串,最长子串等,可以试试使用动态规划。
其实,状态转移方程是个关键。你可以用状态转移表来帮助自己理解整个过程。如果能找到准确的转移方程,那么离最终的代码实现也就不远了 。
这里说下什么是状态转移方程:从上一个状态到下一个状态之间可能存在一些变化,以及基于这些变化的最终决策结果。我们把这样的表达式称为状态转移方程。所有的动态规划算法中,状态转移是关键。
[b]来个例子[/b]
假如有 2 块,3 块,7 块面额的纸币,如何使用最小的纸币数量来凑成 100 块。
一般会优先想到这样的方法:优先使用大面额的,不够的话再用次大面额的,直到凑成 100 块。100 除以 7 = 14 余数为 2 ,正好再用一张 2 的面额就可以了,也就是说最低 15 张。这属于贪心算法,今天先不讲。
动态规划的解题思路:c(n) 表示凑成 n 元的最小纸币数量c(100) = c(93 +7) = c(93)+1c(100) = c(97 +3) = c(97)+1c(100) = c(98 + 2) = c(98)+1
如果分析到这里,你可能会想到递归是一种解决思路,没错,但递归从大到小的分解其实保留了每一步的结果,并没有舍弃非最优解,效率并不高。
接下来就是找 c(93),c(97), c(98)哪个值最小,按照同样的方法,继续进行分解,直到 c(2) = 1,c(3) =1, c(4) = 2, c(5) = 2, c(6)=2, c(7)=1, c(8) = 3。这里可以推出状态转移方程:

状态转移方程

状态转移方程
其中,c[i] 表示总额为 i 的时候,所需要的最少钱币数,其中 j=1,2,3,…,n,表示 n 种面额的钱币,value[j] 表示第 j 种钱币的面额。c[i - values(j)] 表示选择第 j 种钱币的时候,上一步为止最少的钱币数。需要注意的是,i - value(j) 需要大于等于 0,而且 c[0] = 0。

然后,从小到大,我们可以先在草纸上演算下,并验证状态转移方程

验证

验证

接下来的事情就是将这种有规律的过程转化为源代码了,到这里其实已经没有难度了。
  1. #encoding = utf-8

  2. def count_dp(num):
  3.     kinds = [2,3,7]
  4.     ##循环使用tmp,降低内存占用
  5.     tmp = [1,1,2,2,2,1,3]
  6.     result = [[2],[3],[2,2],[2,3],[3,3],[7],[2,3,3]]
  7.     if num <2:
  8.         return
  9.     elif 2 <= num <=8:
  10.         return tmp[num-2],result[num-2]
  11.     else:
  12.         for i in range(9,num+1):
  13.             values=[] #保留三个数据,取最小的那个
  14.             for kind in kinds:
  15.                 values.append( (tmp[(i-2-kind)%7] + 1, (i-2-kind)%7 , kind) )
  16.             min_value = min(values) ##默认按第一个值比较,取最小的
  17.             #print(min_value)
  18.             tmp_result = result[min_value[1]].copy()
  19.             #print(tmp_result)
  20.             tmp_result.append(min_value[2])
  21.             #print(tmp_result)

  22.             ##循环保存在临时数组中
  23.             tmp[(i-2)%7] = min_value[0]
  24.             result[(i-2)%7].clear()
  25.             result[(i-2)%7] = tmp_result.copy()
  26.         return tmp[(num-2)%7],result[(num-2)%7]


  27. def count_recursion(num):
  28.     kinds = [2,3,7]
  29.     ##循环使用tmp,降低内存占用
  30.     tmp = [1,1,2,2,2,1,3]
  31.     if num <2:
  32.         return
  33.     elif 2 <= num <=8:
  34.         return tmp[num-2]
  35.     else:
  36.         ##采用递归方式
  37.         values=[] #保留三个数据,取最小的那个
  38.         for kind in kinds:
  39.             values.append(count_recursion(num - kind))
  40.         min_value = min(values)+1 ##默认按第一个值比较,取最小的
  41.         return min_value

  42. if __name__ == "__main__":
  43.     for i in [1,2,3,4,5,6,7,8,9,10,100]:
  44.         print(i,'->',count_dp(i))

  45.     for i in [1,2,3,4,5,6,7,8,9,15,20]:
  46.         print(i,'->',count_recursion(i))
复制代码
由于递归的方式特别慢,所以只让它运行到 20 。运行结果如下:
  1. 1 -> None
  2. 2 -> (1, [2])
  3. 3 -> (1, [3])
  4. 4 -> (2, [2, 2])
  5. 5 -> (2, [2, 3])
  6. 6 -> (2, [3, 3])
  7. 7 -> (1, [7])
  8. 8 -> (3, [2, 3, 3])
  9. 9 -> (2, [2, 7])
  10. 10 -> (2, [3, 7])
  11. 100 -> (15, [2, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7])
  12. 1 -> None
  13. 2 -> 1
  14. 3 -> 1
  15. 4 -> 2
  16. 5 -> 2
  17. 6 -> 2
  18. 7 -> 1
  19. 8 -> 3
  20. 9 -> 2
  21. 15 -> 4
  22. 20 -> 4
复制代码


注:本文转载自 https://cloud.tencent.com/developer/article/1758788  主题:动态规划-如何推导出状态转移方程

小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2025-6-18 05:53

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表