DP时间复杂度:状态量*每层状态转移的计算量;
当dp要用到上一层的状态如f(i-1)时,下标最好从1开始,下标0直接初始化为0,避免越界。
A:数字三角形
入门题,稍作推导便知道第(i,j)个位置的最大值计算路径必然来自其上的(i-1,j-1)和(i-1,j)两点。
#include <bits/stdc++.h>
using namespace std;
const int N=510,INF=1e9;
int n;
int f[N][N],a[N][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=1;i<=n;i++)
for(int j=0;j<=i+1;j++) //因为计算涉及到(i-1,j-1)(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 ans=-INF;
for(int i=1;i<=n;i++) //遍历最底下一层得到最大值
ans=max(ans,f[n][i]);
cout<<ans<<endl;
return 0;
}
B:最长上升子序列I
模板题 O(n^2)
#include <bits/stdc++.h>
using namespace std;
const int N=1010;
int n,f[N],a[N];
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
for(int i=1;i<=n;i++)
{
f[i]=1;
for(int j=1;j<i;j++)
if(a[j]<a[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;
}
如果要保存最长子序列:
#include <bits/stdc++.h>
using namespace std;
const int N=1010;
int n,f[N],a[N];
int g[N]; //存最长上升子序列
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
for(int i=1;i<=n;i++)
{
f[i]=1; //表示只有本身一个数
g[i]=0;
for(int j=1;j<i;j++)
if(a[j]<a[i])
if(f[i]<f[j]+1)
{
f[i]=f[[j]+1;
g[i]=j; //保存f[k]从哪转移的(下标)
}
}
int k=1; //记下标
for(int i=1;i<=n;i++)
if(f[k]<f[i])
k=i;
cout<<f[k]<<endl;
for(int i=0,len=f[k];i<len;i++) //这是倒序输出(降序)
{
cout<<a[k]<<endl; //因为f[k]表示的是以a[k]结尾的最长上升子序列长度,所以输出a[k]
k=g[k]; //f[k]是从g[k]转移过来的
}
return 0;
}
C:最长上升子序列II
这题把数据加到了n<=100000,显然要对上面的算法进行优化。
(显然我们直接记牢这个算法即可hhh)
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
int n,q[N],a[N];
//q[k]:a序列从第1个元素至第k个元素,所有以a[k]为结尾的上升子序列中,最小的结尾元素值。注意,这里保证了以a[k]结尾的上升子序列的末尾元素一定是最小的!
int main()
{
cin>>n;
for(int i=0;i<n;i++)
cin>>a[i];
int len = 0;//长度为len的上升子序列
q[0]=-2e9;//初始化
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);
q[r+1]=a[i];
}
cout<<len<<endl;
return 0;
}
D:最长公共子序列
f是dp数组,f(i,j)是集合:所有在a序列前i个字母中出现,且在b序列的前j个字母中出现的公共子序列
那么f(i,j)由四种情况转移:
(1)不包含a[i]、b[i] 👉 f[i-1][j-1]
(2)不包含a[i],包含b[i] 👉 f[i-1][j](左边情况含于右式,在求最大值的问题中可以借助右式进行计算)
(3)包含a[i],不包含b[i] 👉 f[i][j-1](同理)
(4)包含a[i]、b[i] 👉 f[i-1][j-1]+1(去掉第a[i]、b[j],在直接加上1,代表a[i]、b[j]已经必选)
注意:情况(1)显然含于情况(2、3),所以可以省略。
#include <bits/stdc++.h>
using namespace std;
const int N=1010;
int n,m;
char a[N],b[N];
int f[N][N];//这里的f是dp数组,f(i,j)是集合:所有在a序列前i个字母中出现,且在b序列的前j个字母中出现的公共子序列
int main()
{
cin>>n>>m;
cin>>a+1>>b+1;
for(int i=1;i<=n;i++) //两层for进行状态枚举
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);
}
cout<<f[n][m]<<endl;
return 0;
}
E:最短编辑距离
这题是公共子序列的应用。
#include <bits/stdc++.h>
using namespace std;
const int N=1010;
char a[N],b[N];
int n,m;
int f[N][N];
//f(i,j)表示a的前i个字母与b的前j个字母相匹配
int main()
{
cin>>n,cin>>a+1;
cin>>m,cin>>b+1;
//初始化f数组
for(int i=0;i<=n;i++)//a的前0~n个长度的串与b的长度为零(就是空的意思)相比,只能删,删i个
f[i][0]=i;
for(int i=0;i<=m;i++)//a的长度为0的串(就是空的意思)与b的前0~n个长度的串相比,只能添,添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);
f[i][j]=min(f[i][j],f[i-1][j-1]+(a[i]!=b[j]));
}
cout<<f[n][m]<<endl;
return 0;
}
F:编辑距离
也是对公共子序列的应用,与上题差不多。
#include <bits/stdc++.h>
using namespace std;
const int N=15,M=1010;
int n,m,f[N][N];
char str[N][N];
int edit_distance(cahr a[],char b[])
{
int la=strlen(a+1),lb=strlen(b+1);//注意这里是从下标1开始
for(int i=0;i<=la;i++) f[i][0]=i; //对dp数组进行初始化
for(int i=0;i<=lb;i++) f[0][i]=i;
for(int i=1;i<=la;i++)
for(int j=1;j<=lb;j++)
{
f[i][j]=min(f[i-1][j]+1,f[i][j-1]+1);//注意这里的写法
f[i][j]=min(f[i][j],f[i-1][j-1]+(a[i]!=b[j]);
}
return f[la][lb];
}
int main()
{
cin>>n>>m;
for(int i=0;i<n;i++)
cin>>str[i]+1;
while(m--)
{
char s[N];
int limit;
cin>>s+1,cin>>limit;
int ans=0;
for(int i=0;i<n;i++)
if(edit_distance(str[i],s)<=limit)
ans++;
cout<<ans<<endl;
}
return 0;
}