DP-LIS

写在前面:学DP掌握基础很重要,这里记录一下LIS和LCS,(希望每次在记录时能够收获新的东西

引入问题:

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

N有这两个数据范围:
1. 1 ≤ N ≤ 1000 1≤N≤1000 1N1000
2. 1 ≤ N ≤ 100000 1≤N≤100000 1N100000

样例
Input:
7
3 1 2 1 8 5 6
output:
4

一.1≤N≤1000

对序列中一个的数a[i]来说,我们定义dp[ i ] 表示以a[i]这个数结尾的最长上升子序列的长度,那么对于下标j∈(1~i-1)这些数来说,如果a[ i ]严格大于a[ j ],就有dp[i] = dp[j]+1,时间复杂度为O(n^2),在这个数据范围内是可以ac的。

#include <iostream>
#include <algorithm>
#include <cstdio>


using namespace std;

const int N = 1010;

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

int main()
{
    cin>>n;
    for(int i = 1; i<=n; i++) cin>>w[i];
    
    for(int i = 1; i<=n; i++)
    {
        dp[i] = 1;
        for(int j = 1; j<=i-1; j++)
            if(w[i]>w[j])
                dp[i] = max(dp[i], dp[j]+1);
    }
    
    int ans = 0;
    for(int i = 1; i<=n; i++) ans = max(ans,dp[i]);
    
    cout<<ans<<endl;
    
    return 0;
}

二.1≤N≤100000

对于这个数据范围来说,上面那种做法显然回超时。那么该如何解决这个问题?
假设我们当前已经走到了第i个数,那么我们肯定想在前i-1个数中拿到最长的那个上升子序列并且可以让a[i]接在后面的,那么我们可以使用一个数组来存储这样一个信息:在前i-1数字中长度为len的子序列的最后一个数字的最小值,为什么是最小值,因为最小值的适用范围更广(如果能接在大的数后面,那么肯定也能接在这个最小值后面),而且,易证,随着len的增加,序列最后的最小值也是严格递增的(反证法可证),那么我们就可以使用二分,找到子序列长度0~len中,最后一个数小于a[ i ]中最大的那一个,然后更新答案。

#include <bits/stdc++.h>
#define IO ios::sync_with_stdio(false);cin.tie(0)
using namespace std;
typedef long long LL;

const int N = 1e5+100;

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

int main()
{
    IO;
    cin>>n;
    for(int i = 1; i<=n; i++) cin>>a[i];
    
    q[0] = -2e9;  //哨兵,当前数比前面的数都小
    int len = 0;
    for(int i = 1; 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,l+1);
        q[l+1] = a[i];  //长度为l+1的子序列的最后一个数更新
    }
    
    cout<<len<<endl;
    return 0;
}

洛谷P1439.最长公共子序列

给出 1,2,…,n 的两个排列 P1和P2 ,求它们的最长公共子序列。
输入
5
3 2 1 4 5
1 2 3 4 5
输出
3
Hint:对于 100% 的数据,n≤10^5

思路:因为是1到n的全排列,所以P1和P2只是元素顺序不同,可以这样转换:
P1: 3 2 1 4 5
change:a b c d e
P2:1 2 3 4 5
=> c b a d e
经过这样一个转换,P1中的元素全是严格单调递增的,那么对于P2,只要将它的元素都改成change中的对应元素,然后求最长上升子序列,那么就一定是两个序列最长的公共子序列

#include <bits/stdc++.h>
#define IO ios::sync_with_stdio(false);cin.tie(0)
using namespace std;
typedef long long LL;

const int N = 1e5+100;
int n;
int b[N],q[N];
unordered_map<int,int> h;

int main()
{
    IO;
    cin>>n;
    for(int i = 1; i<=n; i++){
        int num;
        cin>>num;
        h[num] = i;
    }
    
    for(int i = 1; i<=n; i++){
        cin>>b[i];
        b[i] = h[b[i]];
    }
    
    q[0] = -2e9;
    int len = 0;
    for(int i = 1; i<=n; i++){
        int l = 0, r = len;
        while(l<r){
            int mid = (l+r+1)>>1;
            if(q[mid]<b[i]) l = mid;
            else r = mid-1;
        }
        len = max(len,l+1);
        q[l+1] = b[i];
    }
    cout<<len<<endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值