最长公共子序列(局限性)(LCS)问题

先来个朴素dp算法!见代码注释

点击查看代码
//原理:dp
//时间复杂度:o(n^2),过不了本题
#include <bits/stdc++.h>
using namespace std;
int f[1001][1001];//dp数组,f[i][j]为处理了a的前i位,b的前j位得到的最长公共子序列
int a[1001];
int b[1001];
int main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);//加快输入输出
    int n;
    cin>>n;
    for (int i = 1;i<=n;i++) cin>>a[i];
    for (int i = 1;i<=n;i++) cin>>b[i];
    for (int i = 1;i<=n;i++)//dp,处理f[i][j]
    {
        for (int j = 1;j<=n;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]);//否则转移最大值(观察下标,之前的已经计算过了)
        }
    }
    cout<<f[n][n]<<"\n";//输出答案
    return 0;
}


显然上述解法仅适用于1e4范围 而有没有更快的解法? 当然,对于特殊序列:两个序列元素互异且相同,便可以转化为LIS问题 原理解释及证明: map记录下b[]的每个元素的位置,然后利用a[]得到一个在b中对应元素的位置序列cvt[],为什么可以转化为LIS问题呢? 不妨以a[5] = [3 2 1 4 5],b[5] = [1 2 3 4 5],得到的cvt数组为cvt[5] = [3,2,1,4,5],当然你a,b数组可以反着来操作,无所谓 不妨取cvt中对应一个位置为假设LCS的起点,因为LCS是按顺序来的,那么比起点小的位置均无效,那么除去该起点及所有比起点小的位置,剩下的 情况转换成了一个子问题重复,故动态规划解.而起点又又多个选择,故变成找最长的上升子序列了.故LCS->LIS合理!

下面就是代码了,更多细节见注释.

点击查看代码
#include <bits/stdc++.h>
using namespace std;
map<int,int> b;//用哈希表不一定有红黑树快,对于可能卡哈希的,而红黑树一定过的,优先用红黑树(acmer的严谨,我这里不管)
int a[100001];//a数组
int cvt[100001];//转换得到的cvt数组
int low[100001];//长度为i的LIS的最后一个数的最小值
int m = 0;//LIS最大长度,初始化为0
int Find(int x)//二分查找
{
    int ans = 0;//初始化答案
    int l = 0;
    int r = m;
    while(l<=r)//二分
    {
        int mid = (l+r)>>1;
        if(low[mid] >= x)//一定要等于,否则就不满足LIS性质了(即单调上升,可能会导致单调不减)
        {
            ans = mid;
            r = mid - 1;
        }
        else l = mid + 1;
    }
    return ans;
}
int main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);//加快输入输出
    memset(low,-127,sizeof(low));//low数组初始化为负无穷,防止新的数比low[1]还小的情况的Hack!(自己感悟)
    int n,x;
    cin>>n;//输入数量
    for (int i = 1;i<=n;i++) cin>>a[i];//输入a数组
    for (int i = 1;i<=n;i++)//处理b数组
    {
        cin>>x;
        b[x] = i;//记下位置
    }
    for (int i = 1;i<=n;i++)//生成cvt数组
    {
        cvt[i] = b[a[i]];
    }
    for (int i = 1;i<=n;i++)//LIS模板
    {
        if(cvt[i]>low[m]) low[++m] = cvt[i];//直接接上
        else
        {
            int p = Find(cvt[i]);
            low[p] = cvt[i];//更新位置最小值
        }
    }
    cout<<m<<"\n";//输出答案,即LIS长度
    return 0;
}

posted @ 2024-04-21 22:25  游辰  阅读(16)  评论(0编辑  收藏  举报