常见动态规划模型
六、常见动态规划模型
1、最大字段和
1.1 概念描述
给定一个由数字组成的序列,其中连续的一段子序列称为一个子段,子段中的所有数之和称为子段和,这里只考虑非空子段,即至少包含一个元素的子段,一个序列中子段和最大的子段的子段和,为最大子段和。
1.2动态规划算法分析
对于全部是非正数的序列,最大子段和为其中元素的最大值。
对于有正数的序列,考虑以每一个点为结尾的最大子段和,这个子段一定满足其前缀和均非负,因为如果有一个前缀是负的,那么减掉这个前缀对于这个点一定更优,并且这个子段要尽量往前延伸。
我们可以只用一次扫描,记录目前统计的sum以及答案ans。当sum加上当前位置这个数还是正数的时候就继续累加sum,否则就将sum置为0 。这样就舍掉了所有前缀是负数的情况,并且保证了这个子段尽可能的长,也就是说扫描中记录的sum就是以每一个点为结尾的最大子段和,每一次如果sum比ans大就可以更新ans 。最后ans就是整体的最大子段和。
1.3 代码实现
#include<iostream>
#include<algorithm>
using namespace std;
const int inf = 0x7fffffff;
int num[101];
int main(){
int N;
cin>>N;//读入N和N个变量
for(int i=0;i<N;i++){
cin>>num[i];
}
int ans=-inf;//声明ans并初始化
for(int i=0;i<N;i++){//遍历数组有没有正数,并找出最大值
ans=max(ans,num[i])
}
if(ans<=0){//若ans非负,输出
cout<<ans<<endl;
}else{
int sum=0;//声明sum,并初始化为0
for(int i=0;i<N;i++){
if(sum+num[i]<0){//如果加上后为负数,就舍弃这一段重新开始
sum=0;
}else{//否则将sum加上num[i]
sum+=0;
}
ans=max(ans,sum);//看看是否需要更新
}
}
cout<<ans<<endl;
return 0;
}
2、最长上升子序列(LIS)
2.1 概念描述
在原序列取任意多项,在不改变他们原来数列的先后顺序,得到的序列称为原序列的子序列。最长上升子序列,就是给定的序列的最长的,数值从低到高排列的的子序列。
2.2 算法分析
所以我们需要描述一下这道题,很显然这题用一维就可以去描述了,而这题的阶段就是以第i项作为结尾端点的上升子序列(即dp[i]),而这题的状态就是第i个位置的最长上升子序列的长度,决策的话就是第i个位置是否加入要计算的最长上升子序列(a[j]<a[i],j<i这样的情况才能加入),所以我们就可以推出来他的转移方程:,其实这就是一个带有最优子结构的递推转移方程,如果满足加入最长上升子序列的条件后我们并不一定非得必须加入(因为之前还有可能有以他为结尾的更长的上升子序列)。需要注意的一点是我们需要用两层for循环来找出他的最长上升子序列长度(因为最长上升子序列不是唯一的,所以每一个起点都要去试一下)。
这是递推打表之后得到的模拟表:
2.3 代码实现
#include<iostream>
#include<cstring>
using namespace std;
int dp[101],a[101],n;
int LIS(){
int ans=0;
for(int i=1;i<=n;i++){
dp[i]=1;
for(int j=1;j<i;j++){
if(a[j]<a[i]){
dp[i]=max(dp[i],dp[j]+1);//状态转移方程
}
}
ans=max(ans,dp[i]);//每次内层循环结束后更新ans的值
}
return ans;
}
int main(){
cin>>n;
for(int i;i<=n;i++){
cin>>a[i];
}
cout<<LIS()<<endl;
return 0;
}
3、最长公共子序列
3.1 概念描述
给定两个序列S1和S2,求两者公共子序列S3的最长长度。
3.2 算法分析
这个题目可以按照序列的长度来划分状态,也就是S1的前i个字符和S2的前j个字符的最长公共子序列长度,记为lcs[i][j].
如果S1的第i项和S2 的第j项相同,那么S1[i]与S2[j]作为公共子序列的末尾,则
lcs[i][j]=lcs[i-1][j-1]+1
也可以不让S1[i]与S2[j]作为公共子序列的末尾,则
lcs[i][j]=max(lcs[i][j-1],lcs[i-1][j])
不难证明:
max(lcs[i][j-1],Ics[i-1][j])< Ics[i-1][j-1]+1
那么转移方程是:
举一个例子,两个序列 S1 = abcfbc和S2 = abfcab,根据转移方程可以得出 lcs[i][j]
3.3 代码实现
#include <iostream>
#include <cstring>
#include <string>
using namespace std;
int dp[110][110];//定义一个辅助数组
int main()
{
string a, b;
memset(dp, 0, sizeof(dp));//将dp数组初始化
cin >> a >> b;//输入两个字符串a、b
int lena = a.size();//计算字符串a、b的长度
int lenb = b.size();
for (int i = 1; i <= lena; i++)
{
for (int j = 1; j <= lenb; j++)
{
if (a[i - 1] == b[j - 1])
{
dp[i][j] = dp[i - 1][j - 1] + 1;//dp[i][j]代表a字符串的前i个字符串组成的子串和b字符串的前j个字符串组成的子串LCS
}
else
{
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
cout << dp[lena][lenb] << endl;
return 0;
}