线性动态规划,是较常见的一类动态规划问题,其是在线性结构上进行状态转移,这类问题不像背包问题、区间DP等有固定的模板。
线性动态规划的目标函数为特定变量的线性函数,约束是这些变量的线性不等式或等式,目的是求目标函数的最大值或最小值。
因此,除了少量问题(如:LIS、LCS、LCIS等)有固定的模板外,大部分都要根据实际问题来推导得出答案。(看的其他博主的概述)
我们先从最基础最基础的题来了解线性dp
思路:
分析dp问题无非就两步:
step1:状态表示(难点)
step2:状态计算(也叫状态转移方程)
---------------------------------------------------------------------------------------------------------------------------------
就此题我们也可以按照这个步骤来分析
我们可以将题目看成一个二维数组:如图(略显粗糙)
根据题干我们可以先把你要求到(i,j)这个点的最大值的左上和右上的最大值求出来再加这个终点的值
那么集合划分就是两部分:左上和右上(如图)
!!!注意:f[i-1][j-1]+a[i][j]
f[i-1][j]+a[i][j];
图中忘记加a[i][j];
所以f[i][j]=max(f[i-1][j-1]+a[i][j],f[i-1][j]+a[i][j]);
代码如下:
#include<bits/stdc++.h>
using namespace std;
const int N=510,INf=1e9;
int f[N][N];
int a[N][N];
int n;
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
cin>>a[i][j];
for (int i = 0; i <= n; i ++ )
for (int j = 0; j <= i + 1; j ++ )
f[i][j] = -INf;
f[1][1]=a[1][1];
for (int i = 2; i <= n; i ++ )
for (int j = 1; j <= i; j ++ )
f[i][j] = max(f[i - 1][j - 1] + a[i][j], f[i - 1][j] + a[i][j]);
int res = -INf;
for (int i = 1; i <= n; i ++ ) res = max(res, f[n][i]);
printf("%d\n", res);
return 0;
}
二.最长公共子序列
eg:
思路:
其实这道题的思路和上面的很像。根据前面的步骤一步一步分析。
step1:状态表示,这里我们用二维来表示 f[i][j] :a[i],b[j]的公共子序列的全部集合;
step2:集合划分 根据a[i]和b[j]选没选来划分 分成四个部分
00:都不选
01:选择了b[j];
10:选择了a[i];
11:都选择
代码
#include<bits/stdc++.h>
using namespace std;
const int N=1010;
char a[N],b[N];
int f[N][N];
int n,m;
int main()
{
cin>>n>>m;
scanf("%s%s",a+1,b+1);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
f[i][j]=max(f[i][j-1],f[i-1][j]);
if(a[i]==b[j]) f[i][j]=max(f[i][j],f[i-1][j-1]+1);
}
cout<<f[n][m]<<endl;
return 0;
}
为什么代码实现只有三种情况呢?
因为10和01两种情况里包括了00出现的所有情况可以不写(集合划分可以重复但是不能漏掉元素,但是如果求数量的话集合划分就不可以有重复)
三. 最长上升子序列
(动态规划) O(n2)
状态表示:f[i]表示从第一个数字开始算,以w[i]结尾的最大的上升序列。(以w[i]结尾的所有上升序列中属性为最大值的那一个)
状态计算(集合划分):j∈(0,1,2,..,i-1), 在a[i] > a[j]时,
f[i] = max(f[i], f[j] + 1)。
有一个边界,若前面没有比i小的,f[i]为1(自己为结尾)。
最后在找f[i]的最大值。
代码
#include <iostream>
using namespace std;
const int N = 1010;
int n;
int a[N], f[N];
int main() {
cin >> n;
for (int i = 0; i < n; i++) cin >> a[i];
int res = 1;
for (int i = 0; i < n; i++) {
f[i] = 1; // 设f[i]默认为1,找不到前面数字小于自己的时候就为1
for (int j = 0; j < i; j++) {
if (a[i] > a[j]) f[i] = max(f[i], f[j] + 1); // 前一个小于自己的数结尾的最大上升子序列加上自己,即+1
}
res= max(res, f[i]);
}
cout << res << endl;
return 0;
}
优化
(动态规划 + 二分) O(nlogn)
#include <iostream>
using namespace std;
const int N = 1010;
int n, cnt;
int a[N], f[N];
int main() {
cin >> n;
for (int i = 0 ; i < n; i++) cin >> a[i];
f[cnt++] = a[0];
for (int i = 1; i < n; i++) {
if (a[i] > f[cnt-1]) f[cnt++] = a[i];
else {
int l = 0, r = cnt - 1;
while (l < r) {
int mid = (l + r) >> 1;
if (f[mid] >= a[i]) r = mid;
else l = mid + 1;
}
f[r] = a[i];
}
}
cout << cnt << endl;
return 0;
}
(此文章仅仅包括我学习整理部分 不是全部线性dp问题 以后会写完整的内容)
对于这部分推荐一篇博客