注意:当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;}
