LIS & LCS summary (the longest rising sequence with the longest common subsequence)

\(LIS\)

First, a set of concepts to distinguish
sequences: a subset of sequences, may be continuous or discontinuous.
Substring: a subset of the sequence, it must be continuous.
Rise longest subsequence mean not to say

\ (1 \) naive approach \ (O (n ^ 2) \)

With \ (dp [i] \) represented by \ (I \) length of the sub sequence is the end of the longest
while traversing the let \ (J \) from \ (1 to i-1 \) traversal, if \ (A [i]> a [j] \) then \ (DP [I] = max (DP [I], DP [J] + 1'd) \) , which it is readily apparent, from the foregoing state transition from
the final answer \ (ans = max (ans, dp [i]) \)

\(Code\)

    for(int i=1;i<=n;i++)
    {
        dp[i]=1;//初始化 
        for(int j=1;j<i;j++)//枚举i之前的每一个j 
        if(data[j]<data[i] && dp[i]<dp[j]+1)
        //用if判断是否可以拼凑成上升子序列,
        //并且判断当前状态是否优于之前枚举
        //过的所有状态,如果是,则↓ 
        dp[i]=max(dp[i],dp[j]+1);//更新最优状态 

    }

\ (2 \) piggy-half + \ (O (logn) \)

For the above \ (n ^ 2 \) code that we find so enumerated equivalent transfer, including many meaningless transfer
we \ (dp [len] \) represents the length of \ (len \) maximum the end of the minimum term rise subsequence
Why is the minimum it, we would say that
we still traverse the sequence, the initial default \ (dp \) array assignment to \ (INF \) , \ (len = 1, dp [ . 1] = a [. 1] \) , while recording \ (len \) changes, when \ (a [i]> dp [len] \) represents the number of hours can Jieshangqu, then \ (dp [++ len] = a [i] \ )
obviously, we do so \ (dp [i] \) monotonically increases
then when \ (a [i] <= dp [len] \) , we can not Jieshangqu, but we can consider \ (dp [i] \) to replace \ ((dp [i]> a [i], i <= len) \) , why do it, because we have to replace it after the fall of the smaller , have a greater potential to update the back ,
in order to meet accuracy, we need will be greater than his youngest \ (dp [i] \)Replaced, then it is clear that we can conclude that position with two points, the overall time complexity of the program \ (O (nlogn) \)
for ease of understanding, FIG put a few following
data:
. 11
. 1 100 101 2. 3. 4. 5. 6. 7. 8 9



Jieshangqu



dichotomy found 100 are larger than the minimum value of 4, is the value of the replacement, we found the answer here on the back of the replacement is actually contributing to the smaller end of the current value, the more likely later updated

since 100 has gone , so this dichotomy position 101

behind uninteresting stop back into contact to hold the FIG., the maximum length of 9 significantly.

\(Code\)

#include<cstdio>
#include<iostream>
#include<cstring>
#define maxn 10010
#define re register
#define inf 0x3f3f3f3f
using namespace std;
int dp[maxn],n,a[maxn],len=1;
int main()
{
    scanf("%d",&n);
    memset(dp,inf,sizeof(dp));
    for(re int i=1;i<=n;++i)
    {
        scanf("%d",&a[i]);
    }
    dp[1]=a[1];
    for(re int i=2;i<=n;++i)
    {
        int l=1,r=len,mid;
        if(a[i]>dp[len]) dp[++len]=a[i];
        else
        {
            while(l<=r)
            {
                mid=(l+r)>>1;
                if(dp[mid]>a[i]) r=mid-1;
                else l=mid+1;
            }
        //  if(l<=0) l=1;
            dp[l]=a[i];
        }
    }
    printf("%d",len);
    return 0;
}

Solving the problem of the number and the output path

The number of problem solving is relatively simple (in \ (O (n ^ 2) DP \) ) we in \ (the DP \) after \ (n ^ 2 \) traversing \ (DP \) array, if \ ( DP [J] = DP [I] -1 && A [J] <A [I] \) , then the description \ (dp [i] \) may be made of \ (dp [j] \) transferred from, this time for \ (ans [i] + = ans [j] to \)
Finally, for each end of the line with maximum length is added to

#include <cstdio>
#include <algorithm>
 
using namespace std;
 
const int MAXN = 1000;
typedef pair<int, int> P;
 
int a[MAXN];
P dp[MAXN];     //dp[i].first表示以a[i]结尾的最长上升子序列长度,dp[i].second表示满足该长度的子序列个数
 
int main()
{
    //输入序列
    int n;
    scanf("%d", &n);
    for (int i = 0; i < n; i++) {
        scanf("%d", &a[i]);
    }
    
    int lis = 0;
    for (int i = 0; i < n; i++) {
        dp[i].first = 1;
        for (int j = 0; j < i; j++) {
            if (a[j] < a[i]) {
                dp[i].first = max(dp[i].first, dp[j].first + 1);
            }
        }
        
        //满足最长上升子序列长度的序列个数为长度为dp[i].first-1且a[j]<a[i]的个数
        //即将a[i]填入长度为dp[i].first-1的序列末尾
        for (int j = i - 1; j >= 0; j--) {
            if (dp[j].first == dp[i].first - 1 && a[j] < a[i]) {
                dp[i].second+=dp[j].second;
            }
        }
        
        lis = max(lis, dp[i].first);
    }
    
    printf("%d\n", lis);
    
    int ans = 0;
    for (int i = 0; i < n; i++) {
        if (dp[i].first == lis) {
            ans += dp[i].second;
        }
    }
    
    printf("%d\n", ans);
    
    return 0;
}

Output path: recording as long as the precursor, and then output to the recursive

#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 \) problem

\(LCS\)问题是求两个序列最长的公共子序列,也不一定是连续的
看下面的样例

\(1,4,5\)这个序列在序列\(A,B\)中都存在,是公共子序列,而且是最长的,所以就是我们要求的最长公共子序列

\(O(nm)DP\)

\(dp[i][j]\)表示在第一个串的前\(i\)位和第二个串的前\(j\)位匹配的最长公共子序列
首先我们是在逐个比较,一位一位往前挪动,所以状态的继承应该为\(dp[i][j]=max(dp[i-1][j],dp[i][j-1])\)
如果相同的话,可以由比较的上一位继承而来,也就是\(dp[i][j]=max(dp[i][j],dp[i-1][j-1]+1)\)

那么为什么不能是\(dp[i][j]++\)呢,这有一个反例:

\(s1:a\) \(s2:aa\)

很明显\((1,2)\)的正确答案应该是\(1\),如果由\((1,1)\)继承而来再\(++\)的话就会变成\(2\)
下面是这个过程的流程图

\(Code\)

#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++)
      {
        dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
        if(a1[i]==a2[j])
        dp[i][j]=max(dp[i][j],dp[i-1][j-1]+1);
        //因为更新,所以++; 
      }
    cout<<dp[n][m];
}

例题:\(Luogu P1439\)


题目大意:给出\(1-n\)的两个排列\(P1\)\(P2\),求它们的最长公共子序列。
\(n≤100000\)

这道题用\(O(nm)DP\)明显不行,
我们寻找条件发现题目中说这两个序列都是\(1-n\)的排列,也就是说,将序列\(A , B\)间相同的元素连边,会形成一一对应的映射关系,我们可以用\(map\)数组记录这种映射关系

上方红字表示\(map\)数组

因为最长公共子序列是按位向后比对的,所以a序列每个元素在b序列中的位置如果递增,就说明b中的这个数在a中的这个数整体位置偏后

换而言之,因为\(a\)数组的下标是单调上升的,如果\(map\)数组表示的映射也就是相同的数在\(b\)中的位置也是单调上升的,那么就一定可以构成公共子序列

要求最长公共子序列,只需要在\(map\)数组中求最长上升子序列即可。复杂度\(O(nlogn)\)

当然这种做法仅仅适用于这种特殊题目,当不存在这种一一映射关系时,这种方法能使用。

\(Code\)

#include<cstdio>
#include<iostream>
#include<cstring>
#define maxn 100001
#pragma GCC optimize(3)
#define re register
using namespace std;
int a[maxn],b[maxn],c[maxn],dp[maxn],n,len=1;
int main()
{
    scanf("%d",&n);
    for(re int i=1;i<=n;++i){scanf("%d",&a[i]);}
    for(re int i=1;i<=n;++i){scanf("%d",&b[i]);c[b[i]]=i;}
    memset(dp,0x3f3f3f3f,sizeof(dp));
    dp[1]=c[a[1]];
    for(re int i=2;i<=n;++i)
    {
        int l=1,r=len,mid;
        if(c[a[i]]>dp[len]) dp[++len]=c[a[i]];
        else
        {
            while(l<=r)
            {
                mid=(l+r)>>1;
                if(dp[mid]>c[a[i]]) r=mid-1;
                else l=mid+1;
            }
            dp[l]=c[a[i]];
        }
    }
    
    printf("%d",len);
    return 0;
}

Guess you like

Origin www.cnblogs.com/Liuz8848/p/11226435.html