一些线性DP

闫式DP法,尝试用集合来表示状态。

1.数字三角形

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

用暴搜会超时。

使用DP时,我们需要寻找变化的点并且把它表示出来。这里我们选择从下往上的路线行走,那么对于每个节点,他的不同在于是从他的左下方来到他本身还是从右下方。

 

用二维坐标来表示的话,到达(i,j)的方法有两种,从(i+1,j)或者(i+1,j+1),我们只要取其中较大者即可。

 

假设我们从左边往上方走,把到(i,j)的路径分为两部分,一部分是其本身,一部分是从底到达(i+1,j)这一整块路径,两部分都取得最大即可,即为f(i+1,j)+w(i,j),而到达(i+1,j)路径最大值就是f(i+1,j),这个应是我们按照相同的思维方式一步步计算上来的。

#include<iostream>
#include<cstring>
#include<algorithm>
​
using namespace std;
​
const int N=510;
​
int n;
int w[N][N],f[N][N];
​
int main(){
    cin>>n;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=i;j++){
            cin>>w[i][j];
        }
    }
    for(int i=1;i<=n;i++) f[n][i]=w[n][i];//初始化最底下一行
    for(int i=n-1;i;i--){
        for(int j=1;j<=i;j++){
            f[i][j]=max(f[i+1][j]+w[i][j],f[i+1][j+1]+w[i][j])
        }
    }
    cout<<f[1][1]<<endl;
    
    return 0;
}

简化,可以把w[i][j]加在括号max的外面,然后f和w数组可以合二为一。

2.最长上升子序列

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

分析可知我们把所有数划分为集合,集合为以第i个数结尾的上升子序列。那么f[i]可以用来存以第i个数结尾的数的最大子序列长度。

假设一个数为i,那么f[i]=max(f[j]+1),j=0,1,2,3……i-1。

#include<iostream>
#include<algorithm>
​
using namespace std;
​
const int N=1010;
​
int n;
int a[N],f[N];
​
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    
    for(int i=1;i<=n;i++){
        f[i]=1;//表示以第i个数结尾的数,初始只有i本身
        for(int j=1;j<i;j++){
            if(a[j]<a[i])
                f[i]=max(f[i],f[j]+1);
        }
    }
    int res=0;
    for(int i=1;i<=n;i++) res=max(res,f[i]);
    
    printf("%d\n",res);
    
    retur
}

3.最长上升子序列II

数据范围加大,用上面的算法就会超时,要考虑如何进行优化。

假如在一个数组中,第0个是3,第1个是1,则两者的f都是1,而如果一个数能接到3后面成为一个递增序列,那么它一定能接在1后面,所以3的f可以不求。

以此类推,对于每个长度为x的上升子序列,我们只需要存储其中结尾数字最小的那个即可

#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
int main(void) {
    int n; cin >> n;
    vector<int>arr(n);
    for (int i = 0; i < n; ++i)cin >> arr[i];
​
    vector<int>stk;//模拟堆栈
    stk.push_back(arr[0]);
​
    for (int i = 1; i < n; ++i) {
        if (arr[i] > stk.back())//如果该元素大于栈顶元素,将该元素入栈
            stk.push_back(arr[i]);
        else//替换掉第一个大于或者等于这个数字的那个数
            *lower_bound(stk.begin(), stk.end(), arr[i]) = arr[i];
    }
    cout << stk.size() << endl;
    return 0;
}
/*
例 n: 7
arr : 3 1 2 1 8 5 6
​
stk : 3
​
1 比 3 小
stk : 1
​
2 比 1 大
stk : 1 2
​
1 比 2 小
stk : 1 2
​
8 比 2 大
stk : 1 2 8
​
5 比 8 小
stk : 1 2 5
​
6 比 5 大
stk : 1 2 5 6
​
stk 的长度就是最长递增子序列的长度
​
*/
​
作者:233
链接:https://www.acwing.com/solution/content/3783/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

4.最长公共子序列

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

输入格式

第一行包含两个整数 NN 和 MM。

第二行包含一个长度为 NN 的字符串,表示字符串 AA。

第三行包含一个长度为 MM 的字符串,表示字符串 BB。

字符串均由小写字母构成。

A和B里面所有公共子序列里面最长的是多少?

问题分析 我觉得这题的状态分成两半考虑比较方便,按两个序列末尾的字符是不是相等来区分。

如果两个字符相等,就可以直接转移到f[i-1][j-1],不相等的话,两个字符一定有一个可以抛弃,可以对f[i-1][j],f[i][j-1]两种状态取max来转移。

代码

#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;
}
​
作者:yuechen
链接:https://www.acwing.com/solution/content/8111/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

5.最短编辑距离

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

  1. 删除–将字符串 AA 中的某个字符删除。

  2. 插入–在字符串 AA 的某个位置插入某个字符。

  3. 替换–将字符串 AA 中的某个字符替换为另一个字符。

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

输入格式

第一行包含整数 nn,表示字符串 AA 的长度。

第二行包含一个长度为 nn 的字符串 AA。

第三行包含整数 mm,表示字符串 BB 的长度。

第四行包含一个长度为 mm 的字符串 BB。

字符串中均只包含大写字母。

yxc:动态规划是吧 就是对爆搜的优化嘛 //来自算法基础课

1.状态表示 :f[i][j] 集合:将a[1~i]变成b[1~j]的操作方式有多少次 属性:其中次数最少的 2.状态计算 :从最后一步考虑 有三种操作,所以有三个子集 ok子集划分完了 考虑状态转移的时候 先考虑如果我没有进行这个操作应该是什么状态 然后考虑你进行这一步操作之后会对你下一个状态造成什么影响 然后再加上之前状态表示中你决策出来的那个DP属性 这样就可以自然而然地搞出来转移方程啦

1)删除操作:把a[i]删掉之后a[1~i]和b[1~j]匹配 所以之前要先做到a[1~(i-1)]和b[1~j]匹配 f[i-1][j] + 1 2)插入操作:插入之后a[i]与b[j]完全匹配,所以插入的就是b[j] 那填之前a[1~i]和b[1~(j-1)]匹配 f[i][j-1] + 1 3)替换操作:把a[i]改成b[j]之后想要a[1~i]与b[1~j]匹配 那么修改这一位之前,a[1~(i-1)]应该与b[1~(j-1)]匹配 f[i-1][j-1] + 1 但是如果本来a[i]与b[j]这一位上就相等,那么不用改,即 f[i-1][j-1] + 0

好的那么f[i][j]就由以上三个可能状态转移过来,取个min 细节问题:初始化怎么搞 先考虑有哪些初始化嘛 1.你看看在for遍历的时候需要用到的但是你事先没有的 (往往就是什么0啊1啊之类的)就要预处理 2.如果要找min的话别忘了INF 要找有负数的max的话别忘了-INF

 

作者:LeetCode-Solution 链接:力扣 来源:力扣(LeetCode) 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

PS:上面的是LeetCode上面一道类似的题,只提供删除操作,只是对边界的考虑情况值得借鉴所以贴过来

ok对应的: 1.f[0][i]如果a初始长度就是0,那么只能用插入操作让它变成b f[i][0]同样地,如果b的长度是0,那么a只能用删除操作让它变成b 2.f[i][j] = INF //虽说这里没有用到,但是把考虑到的边界都写上还是保险 最后代码

#include<bits/stdc++.h>
using namespace std;
const int INF = 2e9;
const int N = 2333;
int lena,lenb,f[N][N];
char a[N],b[N];
​
int main() {
    scanf("%d%s",&lena,a+1);
    scanf("%d%s",&lenb,b+1);
    for(register int i=1; i<=lena; i++) 
        for(register int j=1; j<=lenb; j++) 
            f[i][j] = INF;
​
    for(register int i=1; i<=lena; i++) f[i][0] = i;
    for(register int i=1; i<=lenb; i++) f[0][i] = i;
    
    for(register int i=1; i<=lena; i++) {
        for(register int j=1; j<=lenb; 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);//如果原本字符串中这一位不相等,那么就比较插入操作和删掉这一处字符哪个操作更少
            //本质上就是考虑了三种操作哪种更好
        }
    }
    printf("%d\n",f[lena][lenb]);
    return 0;
​
}

作者:Shadow 链接:AcWing 902. 最短编辑距离 - AcWing 来源:AcWing 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值