子序列问题

梦想不抛弃苦心追求的人,只要不停止追求,你们会沐浴在梦想的光辉之中。再美好的梦想与目标,再完美的计划和方案,如果不能尽快在行动中落实,最终只能是纸上谈兵,空想一番。只要瞄准了大方向,坚持不懈地做下去,才能够扫除挡在梦想前面的障碍,实现美好的人生蓝图。子序列问题,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文


LIS:Longest Increasing Subsequence,最长递增子序列
LCS:Longest Common Subsequence,最长公共子序列


一、最长上升子序列(LIS)


1、最长上升子序列的元素不一定相邻 2、最长上升子序列一定是原序列的子集。


1.O(n*n)
对于每一个i,枚举在i之前的每一个元素j,然后对于每一个dp[j],如果元素i大于元素j,那么就可以考虑继承,而最优解的得出则是依靠对于每一个继承而来的dp值,取max.

for(int i=1;i<=n;i++)
    {
        dp[i]=1;//对每一个元素来说,其本身就是一个最长上升子序列。维护一个dp数组,使得dp[i]表示以第i元素为结尾的最长上升子序列长度,那么对于每一个dp[i]而言,初始值即为1
        for(int j=1;j<i;j++)//枚举i之前的每一个j 
        	if(data[j]<data[i] && dp[i]<dp[j]+1)//data存储原序列
        		dp[i]=dp[j]+1;
    }

最后dp[n]即为所求答案
2.O(nlogn)
将原来的dp数组的存储内容由数值换成该序列中,上升子序列长度为i的上升子序列的最小末尾数值。我们当前的上升子序列长度如果已经确定,那么如果这种长度的子序列的结尾元素越小,后面的元素就可以更方便地加入到序列中。

int n;
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        f[i]=0x7fffffff;
        //初始值要设为INF
        /*原因很简单,每遇到一个新的元素时,就跟已经记录的f数组当前所记录的最长
        上升子序列的末尾元素相比较:如果小于此元素,那么就不断向前找,直到找到
        一个刚好比它大的元素,替换;反之如果大于,那么填到末尾元素的下一个q,INF
                就是为了方便向后替换啊!*/ 
    }
    f[1]=a[1];
    int len=1;//通过记录f数组的有效位数,求得个数 
    /*因为上文中所提到我们有可能要不断向前寻找,
    所以可以采用二分查找的策略,这便是将时间复杂
    度降成nlogn级别的关键因素。*/ 
    for(int i=2;i<=n;i++)
    {
        int l=0,r=len,mid;
        if(a[i]>f[len])f[++len]=a[i];
        //如果刚好大于末尾,暂时向后顺次填充 
        else 
        {
        while(l<r)
        {   
            mid=(l+r)/2;
            if(f[mid]>a[i])r=mid;/*如果仍然小于之前所记录的最小末尾,那么不断向前寻找(因为是最长上升子序列,所以f数组必然满足单调)*/
            else l=mid+1; 
        }
        f[l]=min(a[i],f[l]);//更新最小末尾 
        }
    }
    cout<<len;

3.输出序列O(n*n)
记录前驱,然后递归输出(也可以用栈)

#include <iostream>
using namespace std;
const int MAXN = 1000 + 10;
int n, data[MAXN];
int dp[MAXN]; 
int from[MAXN]; 
void output(int x)
{
    if(!x)return;
    output(from[x]);
    cout<<data[x]<<" ";//迭代输出 
}
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)cin>>data[i];
    // DP
    for(int i=1;i<=n;i++)
    {
        dp[i]=1;
        from[i]=0;
        for(int j=1;j<i;j++)
        if(data[j]<data[i] && dp[i]<dp[j]+1)
        {
            dp[i]=dp[j]+1;
            from[i]=j;//逐个记录前驱 
        }
    }

    int ans=dp[1], pos=1;
    for(int i=1;i<=n;i++)
        if(ans<dp[i])
        {
            ans=dp[i];
            pos=i;//由于需要递归输出所以要记录最长上升子序列的最后一个元素,来不断回溯出路径来 
        }
    cout<<ans<<endl;
    output(pos);
    return 0;
}

二、最长公共子序列(LCS)

用dp[i][j]来表示 第一个串的前i位,第二个串的前j位 的LCS的长度
状态转移方程

  1. 如果当前的A1[i]和A2[j]相同(即是有新的公共元素) 那么
dp[i][j]=max(dp[i][j],dp[i-1][j-1]+1);

2.如果不相同(即无法更新公共元素),考虑继承:

dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
#include<iostream>
using namespace std;
int dp[1001][1001],a1[2001],a2[2001],n,m;
int main()
{
    //dp[i][j]表示两个串从头开始,直到第一个串的第i位 
    //和第二个串的第j位最多有多少个公共子元素 
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    	scanf("%d",&a1[i]);
    for(int i=1;i<=m;i++)
    	scanf("%d",&a2[i]);
    for(int i=1;i<=n;i++)
     	for(int j=1;j<=m;j++)
      	{
        	if(a1[i]==a2[j])
        		dp[i][j]=max(dp[i][j],dp[i-1][j-1]+1);
        	else
        		dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
        }
    cout<<dp[n][m];
}

三、LIS转LCS

【洛谷】P1439 【模板】最长公共子序列

关于为什么可以转化成LCS问题,这里提供一个解释。

A:3 2 1 4 5

B:1 2 3 4 5

我们不妨给它们重新标个号:把3标成a,把2标成b,把1标成c……于是变成:

A: a b c d e
B: c b a d e

这样标号之后,LCS长度显然不会改变。但是出现了一个性质:

两个序列的子序列,一定是A的子序列。而A本身就是单调递增的。
因此这个子序列是单调递增的。

换句话说,只要这个子序列在B中单调递增,它就是A的子序列。

所以找B的LCS即可

自此完成转化。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之家整理,本文链接:https://www.bmabk.com/index.php/post/151014.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之家——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!