线性DP(数字三角形,最长上升子序列,最长公共子序列,最短编辑距离,编辑距离

3 篇文章 0 订阅
2 篇文章 0 订阅

线性DP

数字三角形

题意:

给定一个如下图所示的数字三角形,从顶部出发,在每一结点可以选择移动至其左下方的结点或移动至其右下方的结点,一直走到底层,要求找出一条路径,使路径上的数字的和最大。

        7
      3   8
    8   1   0
  2   7   4   4
4   5   2   6   5
思路:

由上往下遍历,遍历方程为 f[i][j] = max(f[i - 1][j - 1] + a[i][j], f[i - 1][j] + a[i][j]);,同时记得初始化状态

代码块:
#include<iostream>
#include<algorithm>

using namespace std;

const int N = 510, INF = 1e9;

int n;
int a[N][N]; // 每点的路径数字
int f[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 = 0; i <= n; i ++ ) {
        for(int j = 0; j <= i + 1; j ++ ) {
            f[i][j] = -INF; // 初始化路径,可能存在负数
        }
    }
    
    f[1][1] = a[1][1]; // 从i = 2 开始
    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 res = - INF;
    for(int i = 1; i <= n; i ++ ) res = max(res, f[n][i]);
    
    cout << res << endl;
    
    return 0;
}

最长上升子序列

题意:

给定一个长度为 N 的数列,求数值严格单调递增的子序列的长度最长是多少。

输入格式

第一行包含整数 N。

第二行包含 N 个整数,表示完整序列。

输出格式

输出一个整数,表示最大长度。

输入样例:
7
3 1 2 1 8 5 6
输出样例:
4
思路:

通过一个状态改变数组来存储每一个数前面的所有小于他并且能够构成上升序列的一个数组,通过替换去改变这个数组(换而言之就是通过改变数组成分,删去某些遍历到该点之后再也用不到的点),如果小于数组尾端的数,则一定可以在数组中找到一个无用之点,通过二分查找去替换。

样例解析:

首先将3入队列

3

然后加入1,1比3大,则3无用

1

放入2,因为2比目前的尾端1大

1 2

考虑1,我们选择替换原来的1,看似未发生变化,但实际含义却发生了变化,同样大小,我们前面的1不可能有超过后面的1的数字多(换而言之,如果是100,前面第一个位数的100的子序列只有他本身,但后面这个一百可能就会有其他的子序列),说了他的含义变化,再来谈谈他实际上没有发生变化,那么也将对我们的数组不产生影响,这么说我们也可以选择不替换(只要我们自己明白就可以),但是选择不替换我们就要多写一个if(去判断是否相等)

【第二次看自己写的东西,发现还是有很多值得补充的地方,开始的时候自己也对之前写的写个内容懵了一下,后来想到了,之所以会换掉的目的就是为了在不改变序列长度的同时让数组变化,变化是具有可变性的比如这个100变成了后面的100,那么说明在100前面所出现的所有小于100的数都是他的子序列,但是作为第一个出现的100是不存在子序列的】

1 2

8直接加入

1 2 8

5比8更好

1 2 5

直接加入6,这里我们就可以很清楚的看出来为什么5比8好了

1 2 5 6
代码块:
#include<iostream>
#include<algorithm>

using namespace std;

const int N = 100010;

int n;
int a[N], f[N]; // 原输入,状态表示
int cnt; // 表示最大的子序列个数

int main()
{
    cin >> n;
    for(int i = 1; i <= n; i ++ ) cin >> a[i];
    
    f[++ cnt ] = a[1];
    for(int i = 2; i <= n; i ++ ) {
        if(a[i] > f[cnt]) f[++ cnt ] = a[i]; // 直接入数组
        else { 
            /*二分查找替换*/
            int l = 1, r = cnt;
            while(l < r) {
                int mid = l + r >> 1;
                if(f[mid] >= a[i]) r = mid;
                else l = mid + 1;
            }
            f[l] = a[i];
        }
    }
    
    cout << cnt << endl;
    return 0;
}

最长公共子序列

题意:

给定两个长度分别为 N 和 M 的字符串 A 和 B,求既是 A 的子序列又是 B 的子序列的字符串长度最长是多少。

输入样例:
4 5
acbd
abedc
输出样例:
3
思路:

通过二维数组来存储使用过a与b的个数状态,存储的是在这个状态里最大的公共子序列数量,然后诶个遍历。

代码块:
#include<iostream>
#include<algorithm>

using namespace std;

const int N = 1010;

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

int main()
{
    cin >> n >> m;
    cin >> a + 1 >> b + 1; // 下标从 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; // 如果相等,我们直接通过未使用a[i],b[j]的状态+1即可
            else f[i][j] = max(f[i - 1][j], f[i][j - 1]); // 如果不相等,但是i, j 依然需要考虑,则选择未使用a[i]但使用b[j],或者未使用b[j]但使用a[i]
        }
    }
    cout << f[n][m] << endl; // 输出遍历完的状态
    return 0;
}

最短编辑距离

题意:

给定两个字符串 A 和 B,现在要将 A 经过若干操作变为 B,可进行的操作有:

  1. 删除–将字符串 A 中的某个字符删除。
  2. 插入–在字符串 A 的某个位置插入某个字符。
  3. 替换–将字符串 A 中的某个字符替换为另一个字符。

现在请你求出,将 A 变为 B 至少需要进行多少次操作。

思路:

状态分析:

记录下在A长度从0 ~ n在B中长度从0 ~ n的各个状态

操作种类:

删,增 :f[i][j] = min(f[i - 1][j] + 1 + f[i][j - 1] + 1)

由a字符串少一个,i - 1 > j时,我就需要多一个删的操作,所以f[i][j] + 1

由b字符串多一个,i > j - 1时,我就需要多一个增的操作,所以f[i][j] + 1

a,b反之亦然

改:f[i][j] = min(f[i][j], f[i - 1][j - 1] + 1)

f[i][j] 前面都已经调整好了,但a[i] != b[j] 就需要发生改的操作

首先,f[0][i]可以直接初始化为 i,f[i][0]可以直接初始化为i,相当于删i次

ok,分析完毕

代码块:
#include<iostream>
#include<algorithm>

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);
    cin >> m >> (b + 1);
    // 初始化为0的状态
    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] = 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;
}

编辑距离

题意:

给定 n 个长度不超过 10 的字符串以及 m 次询问,每次询问给出一个字符串和一个操作次数上限。

对于每次询问,请你求出给定的 n 个字符串中有多少个字符串可以在上限操作次数内经过操作变成询问给出的字符串。

每个对字符串进行的单个字符的插入、删除或替换算作一次操作。

思路:

将上题的思路照搬,不同点是该题的数据范围更小,测试数据更多,因此可以将上题的做法写成函数去判断,当然也可以放在主函数里

代码块:
#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;

const int N = 15, M = 1010;

int n, m;
int f[N][N];
char str[M][N];

int edit_distance(char a[], char b[])
{
    int la = strlen(a + 1), lb = strlen(b + 1);
    
    for(int i = 0; i <= la; i ++ ) f[i][0] = i;
    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);
            if(a[i] == b[j]) f[i][j] = f[i - 1][j - 1];
            else f[i][j] = min(f[i][j], f[i - 1][j - 1] + 1);
        }
    }
    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) >> limit;
        
        int res = 0;
        for(int i = 0; i < n; i ++ )
        {
            if(edit_distance(str[i], s) <= limit) res ++ ;
        }
        cout << res << endl;
    }
    
    return 0;
}
  • 15
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值