每周学习总结: 第四周
本周学习:动态规划的简单dp;
动态规划的三大步骤;
动态规划,就是利用历史记录,来避免我们的重复计算。而这些历史记录,我们得需要一些变量来保存,一般是用一维数组或者二维数组来保存(dp[])。下面是做动态规划题很重要的三个步骤,
定义数组元素的含义,我们会用一个数组,来保存历史数组,假设用一维数组 dp[]吧。这个时候有一个非常非常重要的点,就是规定你这个数组元素的含义,例如你的 dp[i] 是代表什么意思?
2、 找出数组元素之间的关系式(即状态转移方程),我觉得动态规划,还是有一点类似于高中学习时的归纳法的,当我们要计算 dp[n] 时,是可以利用 dp[n-1],dp[n-2]…dp[1],来推出 dp[n] 的,也就是可以利用历史数据来推出新的值,所以我们要找出数组元素之间的关系式,例如 dp[n] = dp[n-1] + dp[n-2],这个就是他们的关系式了。而这一步,也是最难的一步。
3、
找出初始值(即定义边界)。学过数学归纳法的都知道,虽然我们知道了数组元素之间的关系式,例如 dp[n] = dp[n-1] + dp[n-2],我们可以通过 dp[n-1] 和 dp[n-2] 来计算 dp[n],但是,我们得知道初始值,例如一直推下去的话,会由 dp[3] = dp[2] + dp[1]。而 dp[2] 和 dp[1] 是不能再分解的了,所以我们 必须要能够直接获得 dp[2] 和 dp[1] 的值,而这,就是所谓的初始值。
有了初始值,有了状态转移方程,再根据你定义的dp[]数组含义,就可以解题了;
一:最大子序列和问题;
根据上述三个步骤;
1、定义dp[i] 为数组前i个数的和;
2、如果第i个元素大于qiani个元素和,dp[i]=a[i].
得出状态转移方程 dp[i]=max(dp[i-1]+a[i],a[i])
3、定义边界dp[0]=a[0];
题就差不多解出来了。
代码:
#include<iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int a[11000],dp[11000];
int main()
{
int n;
while((cin >> n))
{
if(n==0) break;
for(int i =0;i<n;i++)
{
cin>>a[i];
}
dp[0]=a[0];
for(int i = 1;i<n;i++)
{dp[i]=max(a[i],dp[i-1]+a[i]);
}
int k=0;
for(int i =1;i<n;i++)
{
if(dp[i]>dp[k])
k=i;
}
cout << dp[k]<< endl;
}
}
二 、最长公共子序列(lcs)
问题描述;给出两个字序列a[],b[];求最长公共子序列。
三个步骤:
1、dp[i][j] 代表a 串从起点到i位置的字串 和 b 串从起点到j位置 的字串 的lcs;
2、建立状态转移方程:当a[i]==b[j] 时: dp[i][j]=dp[i-1][j-1]+1;
当 a[i]!=b[j] 时 : dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
3、边界:dp[0][0]=0;
代码:
#include<iostream>
#include <cstdio>
#include <algorithm>
#include<cstring>
using namespace std;
int a[11000],b[11000],dp[11000][11000];
int main()
{
dp[0][0]=0;
string a,b;
int n=strlen(a);
int m =strlen(b);
cin>>a>>b;
for(int i =1 ;i <=n;i++)
for(int j=1;j<=m;j++)
{
if(a[i]==b[j])
dp[i][j]=dp[i-1][j-1]+1;
else
dp[i][j]= max(dp[i-1][j],dp[i][j-1])
cout<<dp[n][m];
}
总的来说:最难的是第二个步骤,如果找到dp各个元素的关系,这个题也差不多就出来啦。
动态规划 多用数组来保留历史信息,以避免重复计算。
第一步的给数组定义 决定第二部建立状态转移方程的难易程度。所以,还是得走一步,看一步。
下周:老师发作业了;
老样子,每天1~2题 ,周末2 ~3题
🆗