最长公共子序列实质(LIS)最长递增子序列 nlogn做法

最长公共子序列

时间限制:1 sec

空间限制:256 MB

问题描述
给定两个 1 到 n 的排列 A,B (即长度为 n 的序列,其中 [1,n] 之间的所有数都出现了恰好一次)。

求它们的最长公共子序列长度。

输入格式

第一行一个整数 n ,意义见题目描述。

第二行 n 个用空格隔开的正整数 A[1],…,A[n],描述排列 A。

第三行 n 个用空格隔开的正整数 B[1],…,B[n],描述排列 B。

输出格式

一行一个整数,表示 A,B 的最长公共子序列的长度。

样例输入

5
1 2 4 3 5
5 2 3 4 1

样例输出

2

样例解释
(2,3) 和 (2,4) 都可以是这两个序列的最长公共子序列。

数据范围
对于 80% 的数据,保证 n<=5,000。

对于 100% 的数据,保证 n<=50,000。

提示
[把 A 中的所有数替换成其在 B 中出现的位置,想一想,新序列的最长上升子序列和所求的东西有什么关系呢?]

另外,为了帮助大家完成题目,我们提供了只包含了输入输出功能的程序模板,也提供了含有算法的大部分实现细节的程序。

你可以根据自己的实际情况,在这些程序的基础上进行作答,或不参考这些程序,这将与你的得分无关。

这些程序可以从【这里】下载。

解析

法一:O(n^2)
dp[i][j] 可以继承dp[i-1][j]&dp[i][j-1] 以及 dp[i-1][j-1]+1
编程注意事项1、边界条件dp[0][j]=0 dp[i][j]=02、for i for j 3、转移 4、答案为dp[n][n]
法二:由于其是排列 转化为最长上升子序列!!LIS
可以采用O(n^2)
但是有一个O(nlogn)的算法。

每次只对fi更新。 f的数组是单调递增的,可以二分查找快速找到位置。

#include <stdio.h>
#include <utility>
#include <math.h>
#include <algorithm>
#include <string.h>
#include <vector>
using namespace std;

// ================= 代码实现开始 =================

const int inf = 1e9;

//pos:b中个元素出现位置
//f:f[i] 表示长度为i的最长公共子序列的末尾最小可能元素
vector<int> pos,f;

// 计算最长公共子序列的长度
// n:表示两序列长度
// a:描述序列 a(这里需要注意的是,由于 a 的下标从 1 开始,因此 a[0] 的值为-1,你可以忽略它的值,只需知道我们从下标 1 开始存放有效信息即可) 
// b:描述序列b(同样地,b[0] 的值为 -1)
// 返回值:最长公共子序列的长度
int LCS(int n, vector<int> a, vector<int> b) {
    //初始化,调整pos,f数组h长度,并将f数组置初值
    pos.resize(n+1);
    f.resize(n+2,inf);
    for(int i=1; i<=n; ++i)
        pos[b[i]] =i;   //记录b序列中各元素出现位置
    for(int i=0; i<=n; ++i)
        a[i]= pos[a[i]];   //如何处理b序列中各元素位置
    f[0]=0; //将f[0]初值置为0
    for(int i=1; i<=n; ++i )
        *lower_bound(f.begin(), f.end(),a[i])=a[i];
        //二分需要修改的f位置并进行修改。
/* lower_bound( begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于或等于num的数字,找到返回该数字的地址,不存在则返回end。
upper_bound( begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于num的数字,找到返回该数字的地址,不存在则返回end。  */
    return int(lower_bound(f.begin(), f.end(),n+1)-f.begin())-1 ;
}

// ================= 代码实现结束 =================

int main() {
    int n, tmp;
    vector<int> a, b;
    scanf("%d", &n);
    a.clear();
    b.clear();
    a.push_back(-1);
    b.push_back(-1);
    for (int i = 1; i <= n; ++i) {
        scanf("%d", &tmp);
        a.push_back(tmp);
    }
    for (int i = 1; i <= n; ++i) {
        scanf("%d", &tmp);
        b.push_back(tmp);
    }
    int ans = LCS(n, a, b);
    printf("%d\n", ans);
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值