注意:当dp要用到上一层的状态如f(i-1)时,下标最好从1开始,下标0直接初始化为0,避免越界。


A:数字三角形

入门题,稍作推导便知道第(i,j)个位置的最大值计算路径必然来自其上的 (i-1, j-1) 和 (i-1, j) 两点。

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. const int N=510,INF=1e9;
  4. int n;
  5. int f[N][N],a[N][N];
  6. int main()
  7. {
  8. cin>>n;
  9. for(int i=1;i<=n;i++)
  10. for(int j=1;j<=i;j++)
  11. cin>>a[i][j];
  12. for(int i=1;i<=n;i++)
  13. for(int j=0;j<=i+1;j++) //因为计算涉及到(i-1,j-1)(i-1,j),要考虑边界在数字三角形周围一圈
  14. f[i][j]=-INF;
  15. f[1][1]=a[1][1]; //不要忘记赋初值
  16. for(int i=2;i<=n;i++)
  17. for(int j=1;j<=i;j++)
  18. f[i][j]=max(f[i-1][j-1]+a[i][j],f[i-1][j]+a[i][j]);
  19. int ans=-INF;
  20. for(int i=1;i<=n;i++) //遍历最底下一层得到最大值
  21. ans=max(ans,f[n][i]);
  22. cout<<ans<<endl;
  23. return 0;
  24. }

B:最长上升子序列I

模板题 O(n^2)

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. const int N=1010;
  4. int n,f[N],a[N];
  5. int main()
  6. {
  7. cin>>n;
  8. for(int i=1;i<=n;i++)
  9. cin>>a[i];
  10. for(int i=1;i<=n;i++)
  11. {
  12. f[i]=1;
  13. for(int j=1;j<i;j++)
  14. if(a[j]<a[i])
  15. f[i]=max(f[i],f[j]+1);
  16. }
  17. int ans=0;
  18. for(int i=1;i<=n;i++)
  19. ans=max(ans,f[i]);
  20. cout<<ans<<endl;
  21. return 0;
  22. }

如果要保存最长子序列:

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. const int N=1010;
  4. int n,f[N],a[N];
  5. int g[N]; //存最长上升子序列
  6. int main()
  7. {
  8. cin>>n;
  9. for(int i=1;i<=n;i++)
  10. cin>>a[i];
  11. for(int i=1;i<=n;i++)
  12. {
  13. f[i]=1; //表示只有本身一个数
  14. g[i]=0;
  15. for(int j=1;j<i;j++)
  16. if(a[j]<a[i])
  17. if(f[i]<f[j]+1)
  18. {
  19. f[i]=f[[j]+1;
  20. g[i]=j; //保存f[k]从哪转移的(下标)
  21. }
  22. }
  23. int k=1; //记下标
  24. for(int i=1;i<=n;i++)
  25. if(f[k]<f[i])
  26. k=i;
  27. cout<<f[k]<<endl;
  28. for(int i=0,len=f[k];i<len;i++) //这是倒序输出(降序)
  29. {
  30. cout<<a[k]<<endl; //因为f[k]表示的是以a[k]结尾的最长上升子序列长度,所以输出a[k]
  31. k=g[k]; //f[k]是从g[k]转移过来的
  32. }
  33. return 0;
  34. }

C:最长上升子序列II

这题把数据加到了n<=100000,显然要对上面的算法进行优化。
(显然我们直接记牢这个算法即可hhh)

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. const int N = 100010;
  4. int n,q[N],a[N];
  5. //q[k]:a序列从第1个元素至第k个元素,所有以a[k]为结尾的上升子序列中,最小的结尾元素值。注意,这里保证了以a[k]结尾的上升子序列的末尾元素一定是最小的!
  6. int main()
  7. {
  8. cin>>n;
  9. for(int i=0;i<n;i++)
  10. cin>>a[i];
  11. int len = 0;//长度为len的上升子序列
  12. q[0]=-2e9;//初始化
  13. for(int i=0;i<n;i++)
  14. {
  15. int l=0,r=len;
  16. while(l<r)//二分查找
  17. {
  18. int mid=l+r+1>>1;
  19. if(q[mid]<a[i]) l=mid;
  20. else r=mid-1;
  21. }
  22. len=max(len,r+1);
  23. q[r+1]=a[i];
  24. }
  25. cout<<len<<endl;
  26. return 0;
  27. }

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),所以可以省略。

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. const int N=1010;
  4. int n,m;
  5. char a[N],b[N];
  6. int f[N][N];//这里的f是dp数组,f(i,j)是集合:所有在a序列前i个字母中出现,且在b序列的前j个字母中出现的公共子序列
  7. int main()
  8. {
  9. cin>>n>>m;
  10. cin>>a+1>>b+1;
  11. for(int i=1;i<=n;i++) //两层for进行状态枚举
  12. for(int j=1;j<=m;j++)
  13. {
  14. f[i][j]=max(f[i-1][j],f[i][j-1]);
  15. if(a[i]==b[j])
  16. f[i][j]=max(f[i][j],f[i-1][j-1]+1);
  17. }
  18. cout<<f[n][m]<<endl;
  19. return 0;
  20. }

E:最短编辑距离

这题是公共子序列的应用。

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. const int N=1010;
  4. char a[N],b[N];
  5. int n,m;
  6. int f[N][N];
  7. //f(i,j)表示a的前i个字母与b的前j个字母相匹配
  8. int main()
  9. {
  10. cin>>n,cin>>a+1;
  11. cin>>m,cin>>b+1;
  12. //初始化f数组
  13. for(int i=0;i<=n;i++)//a的前0~n个长度的串与b的长度为零(就是空的意思)相比,只能删,删i个
  14. f[i][0]=i;
  15. for(int i=0;i<=m;i++)//a的长度为0的串(就是空的意思)与b的前0~n个长度的串相比,只能添,添i个
  16. f[0][i]=i;
  17. for(int i=1;i<=n;i++)
  18. for(int j=1;j<=m;j++)
  19. {
  20. f[i][j]=min(f[i-1][j]+1,f[i][j-1]+1);
  21. f[i][j]=min(f[i][j],f[i-1][j-1]+(a[i]!=b[j]));
  22. }
  23. cout<<f[n][m]<<endl;
  24. return 0;
  25. }

F:编辑距离

也是对公共子序列的应用,与上题差不多。

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. const int N=15,M=1010;
  4. int n,m,f[N][N];
  5. char str[N][N];
  6. int edit_distance(cahr a[],char b[])
  7. {
  8. int la=strlen(a+1),lb=strlen(b+1);//注意这里是从下标1开始
  9. for(int i=0;i<=la;i++) f[i][0]=i; //对dp数组进行初始化
  10. for(int i=0;i<=lb;i++) f[0][i]=i;
  11. for(int i=1;i<=la;i++)
  12. for(int j=1;j<=lb;j++)
  13. {
  14. f[i][j]=min(f[i-1][j]+1,f[i][j-1]+1);//注意这里的写法
  15. f[i][j]=min(f[i][j],f[i-1][j-1]+(a[i]!=b[j]);
  16. }
  17. return f[la][lb];
  18. }
  19. int main()
  20. {
  21. cin>>n>>m;
  22. for(int i=0;i<n;i++)
  23. cin>>str[i]+1;
  24. while(m--)
  25. {
  26. char s[N];
  27. int limit;
  28. cin>>s+1,cin>>limit;
  29. int ans=0;
  30. for(int i=0;i<n;i++)
  31. if(edit_distance(str[i],s)<=limit)
  32. ans++;
  33. cout<<ans<<endl;
  34. }
  35. return 0;
  36. }