线性DP


线性DP

数字三角形

用f[i] [j]表示走到f[i] [j]的最大步数,一共只有两种走法,左上方走下来或右上方走下来,因此我们可以列出状态转移方程

f[i][j] = f[i - 1][j - 1] + a[i][j]; //左上方走下来
f[i][j] = f[i - 1][j] + a[i][j]; //右上方走下来

所以我们只要找到所有点的走法的最大值,再枚举最后一行,也就是出口处的最大值,就是整个三角型的最大值。

#include<bits/stdc++.h>
using namespace std;

const int N=510,INF=0x3f3f3f3f;
int f[N][N];
int a[N][N];

int main(){
    int n;
    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++){          //因为有负数,所以应该将两边也设为-INF
            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]=a[i][j]+max(f[i-1][j-1],f[i-1][j]);
        }
    }

    int res=-INF;
    for(int i=1;i<=n;i++) res=max(res,f[n][i]);
    cout<<res<<endl;
}

逆序写法,更简单

#include <bits/stdc++.h>
using namespace std;

const int N = 510;
int f[N][N];
int n;

int main()
{
    cin >> n;
    for (int i = 1; i <= n; i ++ ){
        for (int j = 1; j <= i; j ++ ){
            cin >> f[i][j];
        }
    }

    for (int i = n; i >= 1; i -- ){
        for (int j = i; j >= 1; j -- ){
            f[i][j] = max(f[i + 1][j], f[i + 1][j + 1]) + f[i][j];
        }
    }

    cout << f[1][1] << endl; // 自下向上一路选最大值选出来的

    return 0;
    
}

最长上升子序列

状态表示 :f[i] 为以i为结尾的最长的上升序列。

状态转移方程

if (w[i] > w[j])
    f[i] = max(f[i], f[j] + 1);

对于每个i,我们都枚举所有小于w[i]的w[j],找出最大的小于w[i]的以w[j]为结尾的f[j]。但最后的点不一定是最大的点,我们还需要遍历一遍,找到f[i]的最大值。

#include <iostream>

using namespace std;

const int N = 1010;

int n;
int w[N], f[N];

int main() {
    cin >> n;
    for (int i = 0; i < n; i++) cin >> w[i];

    int mx = 1;    // 找出所计算的f[i]之中的最大值,边算边找
    for (int i = 0; i < n; i++) {
        f[i] = 1;    // 设f[i]默认为1,找不到前面数字小于自己的时候就为1
        for (int j = 0; j < i; j++) {
            if (w[i] > w[j]) f[i] = max(f[i], f[j] + 1);    // 前一个小于自己的数结尾的最大上升子序列加上自己,即+1
        }
        mx = max(mx, f[i]);
    }

    cout << mx << endl;
    return 0;
}

这是一个O(n^2)的做法,对于过大的数据仍然会超时。

优化(DP+二分)

状态表示:f[i]表示长度为i的单调增子序列中最后一位最小的数字。

如果当前的w[i]比f[cnt - 1]大,说明满足一个单调增的序列,就将他变成当前序列的最后一个数(不一定是最小的),同时cnt++。

如果当前w[i]比f[cnt - 1]小,说明找到一个更好的序列,可以将之前的序列更新,让这个更小的数成为当前的最后一个数。

对于查找,我们可以用二分法找到第一个大于等于w[i]的数字

#include <bits/stdc++.h>
using namespace std;

const int N = 100010;

int n;
int a[N];
int q[N];

int main()
{
    cin >> n;
    for (int i = 0; i < n; i ++ ) scanf("%d", &a[i]);

    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表示r的下一个位置
        q[r + 1] = a[i]; // 更新
    }
    
    cout << len << endl;

    return 0;
}

最长公共子序列

状态划分:两个序列末尾的字符是否相等。

f[i][j] = max(f[i - 1][j], f[i][j - 1]); // 末尾字符不相等
f[i][j] = f[i - 1][j - 1] + 1; // 末尾字符相等

这里摘一下大佬的题解吧:

![`P27X0AXAE{I~$KSS1ECE56](https://s4.ax1x.com/2022/02/11/HawqHJ.png)

#include <iostream>
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++) {
      if (a[i] == b[j]) {
        f[i][j] = f[i - 1][j - 1] + 1;
      } else {
        f[i][j] = max(f[i - 1][j], f[i][j - 1]);
      }
    }
  }
  cout << f[n][m] << '\n';
  return 0;
}

LCS+输出字符串

scanf("%s%s", s + 1, t + 1);
    //cout << strlen(s + 1) << ' ' << strlen(t + 1) << endl;
    for (int i = strlen(s + 1); i >= 1; i -- ){
        for (int j = strlen(t + 1); j >= 1; j -- ){
            if (s[i] == t[j]) f[i][j] = f[i + 1][j + 1] + 1;
            else f[i][j] = max(f[i + 1][j], f[i][j + 1]);
        }
    }
    int i = 1, j = 1;
    while (i <= strlen(s + 1) && j <= strlen(t + 1)){
        if (s[i] == t[j]){
            cout << s[i];
            i ++;
            j ++;
        }
        else if (f[i][j] == f[i + 1][j]) i ++ ;
        else j ++;
    }

最短编辑距离

状态表示f[i] [j] :所有将a[1 ~ i] 变成b[1 ~ j] 的操作方式。

属性:min。

状态计算:

(1)删除:若要从a中删除一个字符,则需要让f[1 ~ i - 1] 与 f[1 ~ j] 匹配,因此

f[i][j] = f[i - 1][j] + 1;

(2)增加:从a中增加一个字符,需要让f[1 ~ i]与f[i ~ j - 1] 匹配

f[i][j] = f[i][j - 1] + 1;

(3)修改:将f[i]改成f[j],让前面的各位刚好匹配

f[i][j] = f[i - 1][j - 1] + 1; // f[i] != f[j]
f[i][j] = f[i - 1][j - 1]; // f[i] == f[j]
#include <bits/stdc++.h>
using namespace std;

const int N = 1010;

int n, m;
char a[N], b[N];
int f[N][N];

int main()
{
    cin >> n >> a + 1 >> m >> b + 1;

    for (int i = 0; i <= n; i ++ ) f[i][0] = i;
    for (int i = 0; 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] << endl;

    return 0;
}

最长公共上升子序列

LIS+LCS的混合问题。

状态表示:

f[i] [j] 代表所有a[1 ~ i]和b[1 ~ j]中以b[j]结尾的公共上升子序列的集合;
f[i] [j]的值等于该集合的子序列中长度的最大值;
状态计算(对应集合划分):

首先依据公共子序列中是否包含a[i],将f[i] [j]所代表的集合划分成两个不重不漏的子集:

不包含a[i]的子集,最大值是f[i - 1] [j];
包含a[i]的子集,将这个子集继续划分,依据是子序列的倒数第二个元素在b[]中是哪个数:
子序列只包含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);
        }
    }
}
#include <bits/stdc++.h>
using namespace std;

const int maxn = 3010;
int a[maxn], b[maxn];
int f[maxn][maxn];
int 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 ans = 0;
    for (int i = 1; i <= n; i ++ ) ans = max(ans, f[n][i]);
    cout << ans << endl;
}

文章作者: Cedeat
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Cedeat !
  目录