动态规划又名为记忆化搜索,就是空间换时间,可以让代码更加高效的运行,换句话说就是,可以暴力解出来的东西,都可以用动态规划来简化运算,下面的代码与讲解就默认为你们对动态规划有一定的了解,对深搜DFS,也有运用经验,只是进阶一下,让你更好的理解。
关于动态规划的题型,有以下几类适用:
1.坐标类 20%
2.序列类 20%
3.划分类 20%
4.区间类 15%
5.背包类 10%
6.最长序列类 5%
7.博弈类 5%
8.综合类 5%
而动态规划的基本就是无非
1.计数(有多少种策略,方法)
2.求最值(最长不连续,连续,递增,递减序列)
3.求是否存在(选择其中几个,是否可以拼出来)
下面开始具体的讲解,大家要是想要更新那个具体的专题,可以评论和我说一声,我一定会尽快更新!!
题目1:
机器人运动 当前为start 移动K 目标 aim 总长度 N 求从start到aim一共有多少种方案 只要不是在终点位置 可以左右移动一格
#include <bits/stdc++.h>
using namespace std;
//机器人运动 当前为start 移动K 目标 aim 总长度 N 求从start到aim一共有多少种方案 只要不是在终点位置 可以左右移动一格
//
int K,N;
int start,aim;
int ans;
int dfs(int s,int k)//当前在s这个位置 还要剩余步骤k
{
if (k==0)// 没有步数了 走不动了 查看当前位置
{
if (s==aim) return 1;
else return 0;
}
if (s==1) return dfs(2,k-1);
if (s==N) return dfs(N-1,k-1);
else return dfs(s+1,k-1)+dfs(s-1,k-1);//加上向左向右走的 种数
}
int main()
{
cin >> start >> aim >> K >> N;
cout << dfs(start,K);//还需K步 当前位置为start
return 0;
}
完全的暴力搜索,分解为子问题之后就好分析了。下面进行一些优化。
记忆化搜索:(简单分析)
#include <bits/stdc++.h>
using namespace std;
//机器人运动 当前为start 移动K 目标 aim 总长度 N 求从start到aim一共有多少种方案 只要不是在终点位置 可以左右移动一格
int K,N;
int start,aim;
int dp[100][100];//s位置 k剩余步数 存在的方法数
int dfs(int s,int k)//当前在s这个位置 还要剩余步骤k
{
// cout << s << " " << k <<" " << dp[s][k] <<endl;
if(dp[s][k]) return dp[s][k];
int res = 0;
if (k==0)// 没有步数了 走不动了 查看当前位置
{
if (s==aim) res=1;
}
else if (s==1) res = dfs(2,k-1);
else if (s==N) res = dfs(N-1,k-1);
else res = dfs(s+1,k-1)+dfs(s-1,k-1);
dp[s][k]=res;
return res;
}
//上面其实有大量的重复计算 每次参数只要s,k相同
// 得出的结果都是一样的 因此可以将得到的结果提前存下来 这样下次调用就可以很方便了
///用二维数组dp存储
//未优化的时间
//1 10 15 20
//350
//--------------------------------
//Process exited after 8.267 seconds with return value 0
//Process exited after 1.786 seconds with return value 0 动态规划 方法二的时间
int main()
{
cin >> start >> aim >> K >> N;
dfs(start,K);
cout << dp[start][K];//还需K步 当前位置为start
return 0;
}
可以明显看出来 效率快了好多 这就是记忆化搜索的魅力,算过一次就没有必要去算了,保存下来,下次遇到,直接调用即可。
下面写的是用动态转移方程写的,对于新手而言,不需要关注动态转移方程是怎么来的,你是想不来的,动态转移只是一种结果,相当于重要的是上面的分析,上面的尝试,然后在熟练的=后而归纳总结的一个方程,过于关注动态转移方程,不去亲手实践,不去模拟,是不可能学好动态规划的。看似很酷的方程后面的努力才是最多的;
#include <bits/stdc++.h>
using namespace std;
int K,N;
int start,aim;
int dp[100][100];//s位置 k剩余步数 存在的方法数
int dfs(int s,int k)//当前在s这个位置 还要剩余步骤k
{
for (int j = 1;j<=k;j++)//剩余的步数
{
dp[aim][0] = 1;//位置为aim 且当前步数为0才是1 除此步数为0的都为0
for (int i = 1;i<=N;i++)//当前处理的位置
{
if (i==1)//位置为1
{
dp[i][j]= dp[2][j-1];
} else if (i==N)
{
dp[i][j]=dp[N-1][j-1];
}else dp[i][j] = dp[i+1][j-1] + dp[i-1][j-1];
}
}
return dp[s][k];
}
//未优化的时间
//1 10 15 20
//350
//--------------------------------
//Process exited after 8.267 seconds with return value 0
//Process exited after 1.786 seconds with return value 0 动态规划 方法二的时间
//Process exited after 1.822 seconds with return value 0 方法三 时间也差不多
int main()
{
cin >> start >> aim >> K >> N;
cout << dfs(start,K);
return 0;
}
上面的是 start = 2,aim = 4,K = 6,N = 5;
画完图存储的就是这些数据,可以直接查询出来,而求值直接用for循环解决。理论上比第二种递归要快,然后代码可以更简洁一些。
多去画画,列一些式子,找找规律。
#include <bits/stdc++.h>
using namespace std;
int K,N;
int start,aim;
int dp[100][100];//s位置 k剩余步数 存在的方法数
int dfs(int s,int k)//当前在s这个位置 还要剩余步骤k
{
dp[aim][0]=1;//步骤为0已经讨论完毕
for (int j = 1;j<=k;j++)//剩余的步数 范围为1~K
{
dp[1][j] = dp[2][j-1];//特殊处理 不要进循环 然后判断了
for (int i = 2;i<N;i++)//当前处理的位置
{
dp[i][j] = dp[i+1][j-1] + dp[i-1][j-1];
}
dp[N][j]=dp[N-1][j-1];
}
return dp[s][k];
}
//1 10 15 20
//350
//--------------------------------
//Process exited after 8.267 seconds with return value 0 方法一 未优化的时间
//Process exited after 1.786 seconds with return value 0 方法二的时间 动态规划
//Process exited after 1.822 seconds with return value 0 方法三 时间也差不多
//Process exited after 1.429 seconds with return value 0 方法四 最优时间
int main()
{
cin >> start >> aim >> K >> N;
cout << dfs(start,K);
return 0;
}
上面的应该是算作正式的动态规划,用了动态转移方程。