用O(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。

 

求最长公共子序列一般来说常见的有o(n^2)的算法。今天向大家介绍一种o(nlong)的方法,(不过此方法有一定的局限性,注意读题)

我们从例子出发讲解,此题有以下样例

5
1 2 4 3 5
5 2 3 4 1

我们记在上面的一串数为数组a,下面的为数组b,如果我们将数组a的每个元素改成这个元素对应在数组b中的位置,

a数组会变成:5 2 4 3 1。

观察新的a数组,我们先观察新的a数组的前两项 5和2,这代表,在原a数组前面的1,2项分别出现在b数组的第5项和第2项,

这构成了交叉,说明这两项不能同时成为构成最长公共子序列的元素。

也就是说,出现在新a数组的逆序对,就表示,所对应的两个元素,在原a数组和b数组的位置相反(出现交叉)

推而广之,求新a数组的最长上升子序列,就是问题的解

 

为了保证问题能在o(nlogn)时间求解,因此我们最长上升子序列也不能用平时常见的算法,而是才用二分法求。

先说步骤再说原理:用一个数组,要求的最长上升子序列第一个元素直接进入数组,接下来,如果当前元素比数组最后一个元素

大,就可以直接进入,如果比数组最后一个元素小,就在数组中用二分查找,找到刚好比它大的元素,做替换。

答案就是最后数组的大小。

下面解释一下为什么这样子可行。

有点懒得写了,这篇博客解释的很清楚https://blog.csdn.net/shuangde800/article/details/7474903。所以就直接借鉴一下啦,嘻嘻

实现代码:

#include <iostream>
#include <vector>
using namespace std;
int st[5004];
int ans = 0;
void push(int x)
{
    st[++ans] = x;
}
// ================= 代码实现开始 =================

/* 请在这里定义你需要的全局变量 */

// 计算最长公共子序列的长度
// n:表示两序列长度
// a:描述序列 a(这里需要注意的是,由于 a 的下标从 1 开始,因此 a[0] 的值为-1,你可以忽略它的值,只需知道我们从下标 1 开始存放有效信息即可)
// b:描述序列b(同样地,b[0] 的值为 -1)
// 返回值:最长公共子序列的长度
int LCS(int n, vector<int> a, vector<int> b)
{
    int pos[5004];
    for( int i=1; i<=n; i++ )
    {
        pos[b[i]] = i; //b相应的元素出现在b数组的位置
    }
    for(int i=1; i<=n; i++)
    {
        a[i] = pos[a[i]];
    }

    //求a数组的最长上升子序列
    for(int i=1; i<=n; i++)
    {
        if( a[i] > st[ans])
            push(a[i]);
        else
        {
            //替换刚好比他大的元素
            int lo = 1; //st的lo
            int hi = ans+1;int mi;
            while( lo < hi)
            {
                mi = (lo + hi) >> 1;
                if( a[i] < st[mi])
                    hi = mi;
                else if( a[i] >= st[mi])
                    lo = mi+1;
            }
            st[lo] = a[i];
        }
    }
    return ans;

}

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

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;
}

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值