1.自然语言描述
线性DP的特点是状态在线性结构上转移;其问题形式多种多样,只有几个典型的问题有固定的模板,更一般的问题是具体问题具体分析。
最长上升子序列LIS:用f[i]表示以i结尾的元素最长上升子序列的长度;状态集合划分,是否能成为原先以s[1],s[2],…,s[i-1]结尾的最长上升子序列的新末尾元素,从而有状态转移方程f[i]=min(f[i],f[j]+1)(1<=j<i,s[j]<s[i])注意根据定义,f[i]初始化为1。
最长公共子序列LCS:用f(i,j)表示序列A以i结尾的前缀和序列B以j结尾的前缀的最长公共子序列的长度;状态集合划分,看A[i]和B[j]是否相等,如果A[i]=B[j],则说明公共子序列长度加1,在f(i-1,j),f(i,j-1),f(i-1,j-1)+1中选最大值;若不相等,则在f(i-1,j),f(i,j-1)中选最大值。
最短编辑距离(K近似匹配):字符串A通过删除、插入和替换三种操作变换到字符串B。用f(i,j)表示字符串A以i结尾的前缀和字符串B以j为结尾的前缀的最短编辑距离(最小改动数)状态集合划分,A(1,i)通过删除A[i]才能做到和B(1,j)匹配所以在这之前A(1,i-1)与B(1,j)已经匹配,则编辑距离为f(i-1,j)+1;A(1,i)通过插入A[i](其实就是B[j])才能做到和B(1,j)匹配所以在这之前A(1,i)与B(1,j-1)已经匹配,则编辑距离为f(i,j-1)+1;类似的,若A[i]!=B[j],A(1,i)要通过替换A[i]进行匹配,那么编辑距离为f(i-1,j-1)+1,若A[i]=B[j],则编辑距离仍为f(i-1,j-1)。
2.代码描述
题目:Acwing.898 数字三角形题目链接
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAXN=510;
int s[MAXN][MAXN],f[MAXN][MAXN];
int main(void)
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
scanf("%d",&s[i][j]);
for(int i=0;i<=n;i++)//初始化f,因为s有负数,所以将f初始化为一个很小的数
for(int j=0;j<=n+1;j++)//注意每行要延伸初始化1个单位,因为三角形右侧边上的元素进行最小值运算时要用,不能初始化为0
f[i][j]=-1e9;
f[1][1]=s[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]+s[i][j],f[i-1][j]+s[i][j]);//从左上方走过来的路径和右上方走过来的路径中取最大值
int ans=0;
for(int i=1;i<=n;i++)//在所有路径中的最大路径值
ans=max(ans,f[n][i]);
cout<<ans<<endl;
return 0;
}
题目:Acwing.895 最长上升子序列题目链接
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAXN=1010;
int n,s[MAXN],f[MAXN];//f[i]记录以i元素结尾的最长上升子序列的长度
int main(void)
{
cin>>n;
for(int i=1;i<=n;i++)
cin>>s[i];
for(int i=1;i<=n;i++){
f[i]=1;
for(int j=1;j<i;j++)
if(s[j]<s[i])
f[i]=max(f[i],f[j]+1);
}
int ans=0;
for(int i=1;i<=n;i++)
ans=max(ans,f[i]);
cout<<ans<<endl;
return 0;
}
题目:Acwing.896 最长上升子序列II题目链接
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAXN=1e5+10;
int n,a[MAXN],q[MAXN];//q存储序列中所有上升子序列的最大元素的最小值
//将所有上升子序列的最后一个元素保存下来,q一定是单调递增的
//证明:若q[i]<=q[i-1],即不是单调递增的,则说明长度为i的上升子序列的第i-1项同样也<=q[i-1];
//q[i-1]并不是最小值,形成矛盾。
//所以q一定单调递增。
//这样,序列的所有元素都寻找当前q中小于自己的最大值的位置,q就确定了。
int main(void)
{
cin>>n;
for(int i=0;i<n;i++)
cin>>a[i];
q[0]=-2e9;//长度为0的最后一个元素初始化为一个很小的数
int len=0;
for(int i=0;i<n;i++){
int l=0,r=len;
while(l<r){
int mid=l+r+1>>1;
if(q[mid]<a[i])
l=mid;
else
r=mid-1;
}
len=max(len,r+1);//长度r变为长度r+1
q[r+1]=a[i];
}
cout<<len<<endl;
// for(int i=1;i<=len;i++)
// cout<<q[i]<<' ';
return 0;
}
题目:Acwing.902 最短编辑距离题目链接
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAXN=1010;
int n,m,f[MAXN][MAXN];//f[i][j]:将a(1~i)串变成b(1~j)串所需要的最少编辑数
char a[MAXN],b[MAXN];
int main(void)
{
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
cin>>m;
for(int j=1;j<=m;j++)
cin>>b[j];
for(int i=0;i<=n;i++)//按照定义初始化f
f[i][0]=i;
for(int j=0;j<=m;j++)
f[0][j]=j;
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);//删除后匹配: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-1][j-1]+1
f[i][j]=min(f[i][j],f[i-1][j-1]+1);
}
cout<<f[n][m]<<endl;
return 0;
}
题目:Acwing.899 编辑距离题目链接
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAXN=15,SIZE=1010;
int n,q,f[MAXN][MAXN];
char s[SIZE][MAXN];
//最短编辑距离的应用,每次询问看所有字符串与询问字符串的最短编辑距离,若能够<=限制,则说明可以在限制次数内进行变换
int main(void)
{
cin>>n>>q;
for(int i=1;i<=n;i++)
cin>> s[i]+1;
while(q--){
char str[MAXN];
int x;
cin>> str+1>>x;
int ans=0;
for(int i=1;i<=n;i++){
memset(f,0,sizeof(f));//每次要初始化
int len_s=strlen(s[i]+1),len_str=strlen(str+1);
for(int j=0;j<=len_s;j++)
f[j][0]=j;
for(int j=0;j<=len_str;j++)
f[0][j]=j;
for(int k=1;k<=len_s;k++)
for(int l=1;l<=len_str;l++){
f[k][l]=min(f[k-1][l]+1,f[k][l-1]+1);
if(s[i][k]!=str[l])
f[k][l]=min(f[k][l],f[k-1][l-1]+1);
else
f[k][l]=min(f[k][l],f[k-1][l-1]);
}
if(f[len_s][len_str]<=x)
ans++;
}
cout<<ans<<endl;
}
return 0;
}