动态规划与递推
部分算法书里存在递推算法的概念。
动态规划和递推的相同点:
- 递推和动态规划都利用了子问题的解来构建原问题的解。它们都基于一种 “由小到大”“由局部到整体” 的思想。
- 两者都有避免重复计算相同子问题的机制。在计算过程中,会记录已经计算过的子问题的解,以便后续直接使用,提高计算效率。
- 它们都适用于解决具有最优子结构性质的问题。最优子结构是指问题的最优解可以由子问题的最优解组合而成。
不同点:
-
递推通常用于解决具有明确的递推关系的问题,这些问题往往有比较规则的数学表达式来描述子问题和原问题之间的关系。
动态规划则更广泛地应用于优化问题,特别是在有多个决策阶段和状态的情况下,它通过定义状态、状态转移方程等来求解最优解。
-
递推一般是基于简单的索引(如数列中的项数)来表示状态,其递推关系相对比较直接。
动态规划需要更复杂的状态表示。通常会定义一个或多个状态变量来描述问题的状态,并且状态转移方程也可能涉及多个条件和决策(比如取最值或特殊条件的值)。
-
递推相对来说更容易理解和建模,对于一些具有明显规律的数学问题或者简单的计数问题,只需要找出递推公式即可。
动态规划的建模相对复杂。需要准确地定义状态、找出状态转移方程和确定边界条件。对于复杂的实际问题,可能还需要对问题进行适当的抽象和简化才能应用动态规划来解决。
这里举几个简单的例子,但个人觉得递推和动态规划的界限其实非常模糊,所以将它们放在一起讨论。
5919. 昆虫繁殖 - AcWing题库
用a[i]
表示第i
个月时虫的数量,b[i]
表示第i
个月蛋的数量。
每对蛋要2个月长大,说明这个月的成虫是上个月的成虫,加上两个月前的蛋。
1对成虫过x
个月产y
对蛋,说明这个月的蛋是x
个月前的成虫产下的蛋。
根据这两个信息,就有了递推方程(或者说状态转移方程):
a[i]=a[i-1]+b[i-2];
b[i]=a[i-x]*y;
以输入样例1 2 8
的递推表来验证方程是否正确:
第几个月 1 2 3 4 5 6 7 8 9
虫的数量 1 1 1 3 5 7 13 23 37
蛋的数量 0 2 2 2 6 10 14 26 46
这里它问的是过z
个月后,所以z=8
实际上是看第9个月的成虫数。也就是说,答案是第z+1
个月时成虫的数量。
参考程序:
#include<iostream>
using namespace std;
int main(){
unsigned long long a[51]={0,1},b[51]={0};
int x,y,z;
cin>>x>>y>>z;
for(int i=1;i<=x;i++) a[i]=1;//前x个月虫子还不能下蛋
for(int i=x+1;i<=z+1;i++){
a[i]=a[i-1]+b[i-2];
b[i]=a[i-x]*y;
}
cout<<a[z+1];
return 0;
}
5920. 位数问题 - AcWing题库
1000位数肯定不能直接枚举所有情况,所以找规律。
- 状态定义
设a[i]
表示i
位数时偶数个3的情况种数,b[i]
则表示i
位数时奇数个3的情况种数。
1位数的情况时:
偶数个3的情况:0,1,2,4,5,6,7,8,9
这9个数有偶数个3(0也是偶数)。所以a[1]=3
。
奇数个3的情况:就一个3。所以b[1]=1
。
2位数的情况:
可以在1位数的基础上,往1位数之前放数字组成2位数。如果只统计2位数的情况,则第1位不能放0;如果统计到3位数及以上,则可以放0。
首先是只统计到2的情况:
a[2]=8*a[1]+b[1]=8*9+1=73
,即在1位的9个偶数个3的情况的基础上,往前放除了0和3的另外8个数字,此时有72种情况;而在奇数个3的情况的基础上,再放1个3,就又是偶数个情况。
b[2]=a[1]+8*b[1]=9+8*1=17
。即在偶数个3的基础上放一个3,以及在奇数个3的基础上放除了0和3之外的数。
若统计到3位及以上时,统计到2的情况:
因为要把0算进去,所以
a[2]=9*a[1]+b[1]=9*9+1=82
,b[2]=a[1]+9*b[1]=9+9*1=18
。
- 状态转移方程
当还没统计到第N
位数时,转移方程:
a[i]=9a[i-1]+b[i-1];
b[i]=a[i-1]+9*b[i-1];
当统计到第N
位时,转移方程:
a[i]=8a[i-1]+b[i-1];
b[i]=a[i-1]+8*b[i-1];
其中a[1]=9, b[1]=1
。因此在递推的时候用第1个,当到最后一步时用第2个。
参考程序:
#include<iostream>
#include<algorithm>
using namespace std;
void f() {//枚举个数找规律用
int a = 0, b = 0;
for (int i = 100; i < 1000; i++) {
int t = i; int cnt = 0;
if (i == 0) {
a++; continue;
}
while (t) {
if (t % 10 == 3) cnt++;
t /= 10;
}
if (cnt % 2) b++; else a++;
}
cout << a << ' ' << b;
}
int main() {//dp
int a[1001] = { 0,9 }, b[1001] = { 0,1 };
int flag = 9;
int n;
cin >> n;
for (int i = 2; i <= n; i++) {
if (i == n) --flag;//走到最后一步时,不再把0考虑进去
a[i] = (flag * a[i - 1] + b[i-1])%12345;
b[i] = (a[i - 1] + flag * b[i - 1])%12345;
}
cout << a[n];
return 0;
}