线性dp是简单的动态规划,可以主要将问题拆分来看,分成状态表示和状态计算。
通过一个经典题目---数字三角形来分析
给定一个如下图所示的数字三角形,从顶部出发,在每一结点可以选择移动至其左下方的结点或移动至其右下方的结点,一直走到底层,要求找出一条路径,使路径上的数字的和最大。
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
输入格式
第一行包含整数 n,表示数字三角形的层数。
接下来 n行,每行包含若干整数,其中第 i 行表示数字三角形第 i 层包含的整数。
输出格式
输出一个整数,表示最大的路径数字和。
数据范围
1≤n≤500
−10000≤三角形中的整数≤10000
输入样例:
5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
输出样例:
30
分析:
若是想求到a[i][j]的最大值,一定有两种来源,左上和右上,左上方a[i-1][j-1],右上a[i-1][j],判断到左上和右上的状态,那个最大即可,最后再加上当前值。
#include <iostream>
using namespace std;
const int N=510,INF=1e9;
int n;
int a[N][N]; //保存每个位置的值
int f[N][N]; //f[i][j]表示从(1,1)走到(i,j)的所有路径中,总和最大的那一条路径的总和
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
//判断从一开始还是从0开始,看后面如果出现i-1等状态,最好是i>=1,这样i=1时,i-1=0,可以为f[0]边界赋值,初始化;若没有出现i-1的状态,可以考虑从0开始
for(int j=1;j<=i;j++)
scanf("%d",&a[i][j]);
for(int i=1;i<=n;i++)
for(int j=0;j<=i+1;j++) //这里j从0到i+1,因为对于边界点,它的上一层只有一条路径通向它
f[i][j]=-INF; //为了避免边界问题,初始化近似为-∞
f[1][1]=a[1][1]; //由f[i][j]的定义,(1,1)点的f值就是本身
for(int i=2;i<=n;i++) //从第二层开始枚举至第n层
for(int j=1;j<=i;j++)
f[i][j]=max(f[i-1][j-1],f[i-1][j])+a[i][j];
int res=-INF;
for(int i=1;i<=n;i++) res=max(res,f[n][i]); //最大值在第n层的某一个点取得,取最大值
printf("%d",res);
return 0;
}
最长上升序列:
给定一个长度为 N 的数列,求数值严格单调递增的子序列的长度最长是多少。
输入格式
第一行包含整数 N。
第二行包含 N 个整数,表示完整序列。
输出格式
输出一个整数,表示最大长度。
数据范围
1≤N≤1000
−109≤数列中的数≤109
输入样例:
7
3 1 2 1 8 5 6
输出样例:
4
分析:这道题也是经典的dp问题,还是关键的状态表示和状态计算,在这里,将第a[i]个值的前面数列进行遍历,这里f[n]状态表示为 a[i] 结尾的最长上升子序列的长度,若只有i一个值是最长上升序列,那么f[i]=1,长度只有1,把前 i−1个数字中所有满足条件 a[j]<a[i]) 的 j 找出来,那么 f[i] 就是更新为以 a[j] 结尾的最长上升子序列的长度 再加上 自己的长度 1,比较更新前后的最大值即可。
#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int a[N],f[N]; //a[N]记录序列,f[N]表示最长序列时的长度
int main()
{
int n;
cin>>n;
for(int i=0; i<n; i++)
{
cin>>a[i]; //输入序列
}
int m=1;
for(int i=0; i<n; i++)
{
f[i]=1; //若只有a[i],那么长度为1,找不到前面数字小于自己的时候就赋值1
for(int j=0; j<i; j++)
{
if(a[i]>a[j]) f[i]=max(f[i],f[j]+1); // (前一个小于自己的数加上a[i]自己,f[i]长度即+1 )和前一个未更新的状态比较
}
m=max(m,f[i]);
}
cout<<m<<endl;
return 0;
}