线性dp,是较常见的一类动态规划问题,其是在线性结构上进行状态转移,这类问题不像背包问题、区间DP等有固定的模板。
线性动态规划的目标函数为特定变量的线性函数,约束是这些变量的线性不等式或等式,目的是求目标函数的最大值或最小值。
因此,除了少量问题(如:LIS、LCS、LCIS等)有固定的模板外,大部分都要根据实际问题来推导得出答案。
我总结了几种比较普遍的线性dp模型,包括序列模型,字符串编辑模型,求和模型这三种模型,以下解法都是最简单的没有经过优化的解法,因为这样更容易看懂。分析DP问题的方法采用了闫氏DP分析法。
#include
#include
#include
using namespace std;
const int N = 1010;
int n;
int a[N];
int f[N];
int main()
{
cin>>n;
for (int i = 1; i <= n; i ++ ) cin>>a[i],f[i]=1;
for(int i=1;i<=n;i++)
for(int k=1;k<i;k++){
if(a[i]>a[k]) f[i]=max(f[i],f[k]+1);
}
int res=1;
for(int i=1;i<=n;i++) res=max(res,f[i]);
cout<<res;
return 0;
}
#include
#include
#include
using namespace std;
const int N = 1010;
int n,m;
char a[N],b[N];
int f[N][N];
int main()
{
cin>>n>>m>>(a+1)>>(b+1);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
f[i][j]=max(f[i-1][j],f[i][j-1]);
if(a[i]==b[j]) f[i][j]=max(f[i][j],f[i-1][j-1]+1);
}
int res=0;
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ ) res=max(res,f[i][j]);
cout<<res;
return 0;
}
问题描述
解题思路
这道题比较难,而且不优化还过不了,就不在图里写解释过程了。
首先这题的状态定义就比较特别:和最长公共子序列相比,这里还多了一个以b[j]结尾的公共子序列
在集合划分上首先的依据的是a[i]是否包含在集合中
1.a[i]不包含在集合中
:这个比较好理解就是f[i-1][j]
2.a[i]包含在集合中
:首先,如a[i]包含在集合中,那么a[i]==b[j]
,a[i]的前一个数就是a[i-1],对于b[j]的前一个数,需要继续划分。之所以要继续划分是因为一定要抓住集合定义来做,这里的集合中j表示的是以b[j]结尾的公共子序列
,那么在这个公共子序列中它的前一个数是哪个我们不知道,我们需要枚举k个(k=1~j-1)中每个以b[k]结尾的公共子序列
,取其中的最大值即可:
子序列中只包含b[j]
一个数,长度是1
子序列的第一个数是b[1]
的集合,最大长度是f[i - 1][1] + 1
;
…
子序列的倒数第二个数是b[j - 1]
的集合,最大长度是f[i - 1][j - 1] + 1
;
按照上述思路实现需要三重循环
for (int i = 1; i <= n; i ++ )
{
for (int j = 1; j <= n; j ++ )
{
f[i][j] = f[i - 1][j];
if (a[i] == b[j])
{
int maxv = 1;
for (int k = 1; k < j; k ++ )
if (a[i] > b[k])
maxv = max(maxv, f[i - 1][k] + 1);
f[i][j] = max(f[i][j], maxv);
}
}
}
时间复杂度到达了n的三次方
。因此需要对代码做等价变形来优化
我们发现每次循环求得的maxv
是满足a[i] > b[k]的f[i - 1][k] + 1
的前缀最大值。
因此可以直接将maxv
提到第一层循环外面,减少重复计算,此时只剩下两重循环。
最终枚举子序列结尾取最大值即可
闫氏DP分析法的图见下
AC代码
#include
#include
#include
using namespace std;
const int N = 3005;
int n;
int a[N],b[N];
int f[N][N];
int main()
{
cin>>n;
for (int i = 1; i <= n; i ++ ) cin>>a[i];
for (int i = 1; i <= n; i ++ ) cin>>b[i];
for(int i=1;i<=n;i++)
{
int maxv=1;
for(int j=1;j<=n;j++)
{
f[i][j]=f[i-1][j];
if(a[i]==b[j]) f[i][j]=max(f[i][j],maxv);
if(a[i]>b[j]) maxv=max(maxv,f[i-1][j]+1);
}
}
int res=0;
for (int i = 1; i <= n; i ++ ) res=max(res,f[n][i]);
cout<<res;
return 0;
}
问题描述
解题思路
闫氏DP分析法的过程见下图
1)删除操作
:把a[i]删掉之后a[1i]和b[1j]匹配
所以之前要先做到a[1(i-1)]和b[1j]匹配
f[i-1][j] + 1
2)插入操作
:插入之后a[i]与b[j]完全匹配,所以插入的就是b[j]
那填之前a[1i]和b[1(j-1)]匹配
f[i][j-1] + 1
3)替换操作
:把a[i]改成b[j]之后想要a[1i]与b[1j]匹配
那么修改这一位之前,a[1(i-1)]应该与b[1(j-1)]匹配
f[i-1][j-1] + 1
但是如果本来a[i]与b[j]这一位上就相等,那么不用改,即
f[i-1][j-1] + 0
然后有一些细节问题,就是初始化
的时候
如果a序列的长度是i,然后b序列的长度是0,那么想让a变b则需要进行i次删除操作
如果a序列的长度是0,b序列的长度是i,那么想让a变b则需要进行i次插入操作
好的那么f[i][j]就由以上三个可能状态转移过来,取个min
AC代码
#include
#include
#include
using namespace std;
const int N = 1010;
int n,m;
char a[N],b[N];
int f[N][N];
int main()
{
cin>>n;
for (int i = 1; i <= n; i ++ ) cin>>a[i];
cin>>m;
for (int i = 1; i <= m; i ++ ) cin>>b[i];
for (int i = 1; i <= n; i ++ ) f[i][0]=i;
for (int i = 1; i <= m; i ++ ) f[0][i]=i;
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
{
f[i][j]=min(f[i-1][j]+1,f[i][j-1]+1);
if(a[i]==b[j]) f[i][j]=min(f[i][j],f[i-1][j-1]);
else f[i][j]=min(f[i][j],f[i-1][j-1]+1);
}
cout<<f[n][m];
return 0;
}
问题描述
解题思路
就是上一题编辑距离的一个应用,直接写就行,见如下代码
AC代码
#include
#include
#include
using namespace std;
const int N = 1010;
int n,m;
char a[N][15];
int f[N][N];
int edit_distance(char a[],char b[])//完全就是按照最短编辑距离的写法来写
{
int lena=strlen(a+1);
int lenb=strlen(b+1);
for(int i=1;i<=lena;i++) f[i][0]=i;
for(int i=1;i<=lenb;i++) f[0][i]=i;
for(int i=1;i<=lena;i++)
for(int j=1;j<=lenb;j++)
{
f[i][j]=min(f[i-1][j]+1,f[i][j-1]+1);
if(a[i]==b[j]) f[i][j]=min(f[i][j],f[i-1][j-1]);
else f[i][j]=min(f[i][j],f[i-1][j-1]+1);
}
return f[lena][lenb];
}
int main()
{
cin>>n>>m;
for (int i = 1; i <= n; i ++ ) cin>>(a[i]+1);
while (m -- )
{
char str[15];
int q,res=0;
cin>>(str+1)>>q;
for (int i = 1; i <= n; i ++ )
{
if(edit_distance(str,a[i])<=q) res++;//对于每一个询问的字符
//计算它与前面n个字符串的最短编辑距离,如果小于q,计数器=1
}
cout<<res<<endl;
}
return 0;
}
问题描述
解题思路
闫氏DP分析法如下图
这个比较简单,在集合划分的时候,注意到分两种决策:一种是抢劫当前的店铺,另一种是不抢劫
1.抢劫当前店铺:那么在进行状态转移的时候,它的上一个状态就不能从f[i-1]
这里转移过来,因为抢劫相邻店铺会报警。所以是f[i]=f[i-2]+a[i]
2.不抢劫当前店铺:当前的f[i]=f[i-1]
两者取max即可
细节上注意初始化,f[1]=a[1]
,f[2]=max(a[1],a[2])
AC代码
#include
using namespace std;
const int N=1e5+10;
int n,t;
int a[N];
int f[N];
int main()
{
cin>>t;
while(t--)
{
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
f[1]=a[1];
f[2]=max(a[1],a[2]);
for(int i=3;i<=n;i++)
{
f[i]=max(f[i-1],f[i-2]+a[i]);
}
cout<<f[n]<<endl;
}
return 0;
}
问题描述
解题思路
闫氏DP分析法见下图
这题也比较简单,状态定义的时候就是二维矩阵的坐标。然后会从两个方向转移过来,从上方和从左边,取一个最大值再加上当前的值即可
AC代码
#include
#include
#include
using namespace std;
const int N = 1010;
int t,n,m;
int a[N][N];
int f[N][N];
int main()
{
cin>>t;
while(t--)
{
cin>>n>>m;
for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) cin>>a[i][j];
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
f[i][j]=max(f[i-1][j],f[i][j-1])+a[i][j];
}
cout<<f[n][m]<<endl;
}
return 0;
}
线性DP这一块蓝桥杯什么的还是非常喜欢考的,可以考得很难,也可以考得简单,我还要多刷刷题(ง •_•)ง