Longest common ascending subsequence (LCIS) algorithm and optimization of dynamic programming & & Openjudge2000: longest common ascending subsequence

Posted by krishnam1981 on Thu, 10 Feb 2022 22:18:14 +0100

Longest common ascending subsequence (LCIS) algorithm and Optimization for dynamic programming

  • Examples

Title portal

describe
Given two integer sequences, write a program to find their longest rising common subsequence.
When the following conditions are met, we will sequence S1, S2, SN is called sequence A1, A2, Ascending subsequence of AM: 1 < = I1 < I2 << In < = M, so that for all 1 < = J < = N, there is Sj = Aij, and for all 1 < = J < N, there is SJ < SJ + 1.
input
Each sequence is represented by two lines. The first line is the length m (1 < = m < = 500), and the second line is the M integers AI (- 231 < = AI < 231) of the sequence
output
In the first line, the length L of the longest rising common subsequence of the two sequences is output. In the second line, the subsequence is output. If there is more than one qualified subsequence, any one can be output.
Sample input / output

5
1 4 2 5 -12
4
-12 1 2 4

2
1 4

  • problem analysis

: this problem is to combine the LCS and LIS learned before, so it is not difficult to get the state transition equation of this problem by referring to the state transition equation of LCs before:
① a[i] != b[j], dp[i][j] = dp[i-1][j]
② a[i] == b[j], dp[i][j] = max(dp[i-1][k]+1) (1 <= k <= j-1 && b[j] > b[k])
The situation of a[i]==b[i] here is basically the same as that of LCs, except that K is added to ensure that the number of transfers between 1 and j-1 is less than b[j] (the same principle as LIS). If you don't understand it here, LIS doesn't understand it well. You can refer to other LIS blogs. a[i]!= The situation of B [i] is different from LCS, because dp[i][j] is an LCIS ending with b[j]. If dp[i][j] > 0, it means that there must be an integer a[k] in a[1]... A [i] equal to b[j], because a[k]= A [i], then a [i] has no contribution to dp[i][j], so we can still get the optimal value of dp[i][j] without considering it. So in a [i]= In the case of b[j], there must be dp[i][j] == dp[i-1][j], so this also explains why there is no case of dp[i][j]=dp[i][j-1] in LCs in LCIS.

  • Code implementation and optimization

According to the state transition equation and analysis, it is not difficult to write the simplest O(N * M^2) algorithm
code:

int LCIS(int *a,int n,int *b,int m) 
{
    int ans=0;
    int dp[505][505]={0};
    int tmp=0;
    for (int i=0;i<n;i++) 
	{
        for(int j=0;j<m;j++) 
		{
            dp[i+1][j+1]=dp[i][j+1];
            if(a[i]==b[j]) 
			{
                tmp=0;
                for(int k=0;k<j;k++) 
				{
                    if(b[j]>b[k]&&tmp<=dp[i][k+1]) tmp=dp[i][k+1];
                }
                dp[i+1][j+1]=tmp+1;
            }
            ans=ans>dp[i+1][j+1]?ans:dp[i+1][j+1];
        }
    }
    return ans;
}

Optimization 1: when enumerating and finding k here, like LIS optimization, adding dichotomy can reduce the complexity to O(N * M * log(M), but this is not a positive solution, so I didn't post code... I'm too lazy to write
Optimization 2: if the search cannot be optimized, analyze the problem:
When a[i] == b[j], DP [i] [J] = max (DP [I-1] [k] + 1) (1 < = k < = J-1 & & B [J] > b[k]), we find a feature: in fact, the relationship between a [i] (B [J]) and b[k] can be determined long ago! (I is the outermost loop and j is the inner loop. When J traverses K, it is enough to judge the size relationship of b[j]b[j]). Therefore, we only need to directly maintain a tmp value between the inner loop and the outer loop. When a[i] == b[j], we can directly make dp[i][j] = tmp+1, and the time complexity is reduced to O(N * M)!
code:

int LCIS(int *a,int n,int *b,int m) 
{
    int ans=0;
    int dp[505][505]={0};
    for(int i=0;i<n;i++) 
	{
        int tmp=0;
        for(int j=0;j<m;j++) 
		{
            dp[i+1][j+1]=dp[i][j+1];
            if(a[i]>b[j]&&tmp<dp[i+1][j+1]) tmp=dp[i+1][j+1];
            if(a[i]==b[j]) 
			{
                dp[i+1][j+1]=tmp+1;
            }
            ans=max(ans,dp[i+1][j+1]);
        }
    }
    return ans;
}

Optimization 3: the time complexity cannot be optimized (it is said that the tree array can be optimized to O(Nlog(M))? But i won't), but the space is OK. It can be found that we only use i+1 and i in the first dimension of dp array, so we can reduce the space to O (M) by rolling array
code:

int LCIS(int *a,int n,int *b,int m) 
{
    int ans=0;
    int dp[2][505]={0};
    for(int i=0;i<n;i++) 
	{
        int tmp=0;
        for (int j=0;j<m;j++) 
		{
            dp[(i+1)%2][j+1]=dp[i%2][j+1];
            if(a[i]>b[j]&&tmp<dp[(i + 1) % 2][j+1]) tmp=dp[(i+1)%2][j+1];
            if(a[i]==b[j]) dp[(i+1)%2][j+1]=tmp+1;
            ans=max(ans,dp[(i+1)%2][j+1]);
        }
    }
    return ans;
}

After learning this way, we can happily cut off the water wave experience of the above example, but we find that we need to output the path, so use the array to record the transfer precursor and output it recursively! (for those unfamiliar with recursive output, please refer to My other blog)
code:

#include<bits/stdc++.h>
using namespace std;
int n,m,a[505],b[505],dp[505],p[505],ans;
void LCIS(int x)
{
    if(p[x])LCIS(p[x]);
    cout<<a[x]<<" ";
    return;
}
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i];
    cin>>m;
    for(int i=1;i<=m;i++) cin>>b[i];
    int pos=0,tmp;
    for(int i=1;i<=n;i++)
    {
        tmp=0;
        for(int j=1;j<=m;j++)
        {
            if(a[i]>b[j]&&dp[j]>dp[tmp]) tmp=j;
            if(a[i]==b[j])
            {
                dp[j]=dp[tmp] + 1;
                p[j]=tmp;
            }
        }
    }
    for(int i=1;i<=m;i++)
    {
        if(dp[i]>dp[pos]) pos=i;
    }
    cout<<dp[pos]<<"\n";
    if(dp[pos]) LCIS(pos);
    return 0;    
}

Note: I wrote "code" instead of "AC code", because the LJ evaluation machine of SBOpenjudge does not have Special judge, so I wa
After looking for it for a long time, I didn't know how to change it, so I just copied an AC code on the Internet... (this guy uses the vector output path)
AC Code:

#include<bits/stdc++.h> 
using namespace std;  
struct Node 
{  
    int val=0;  
    vector<int>v;  
};  
int main() 
{  
    int a[501],b[501];  
    Node dp[501];  
    int m,n;  
    cin>>m;  
    for(int i=1;i<=m;i++) cin>>a[i];  
    cin>>n;  
    for(int i=1;i<=n;i++) cin>>b[i];  
    for (int i=1;i<=n;i++)  
    {  
        Node Max;  
        for (int j=1;j<=m;j++)
		{  
            if(b[i]>a[j]&&dp[j].val>Max.val) Max=dp[j];  
            if (b[i]==a[j]) 
			{  
                dp[j].val=Max.val+1;  
                dp[j].v=Max.v;  
                dp[j].v.push_back(b[i]);  
            }  
        }  
    }  
    Node Max=dp[1];  
    for (int i=2;i<=m;i++) 
	{  
        if(dp[i].val>Max.val) Max=dp[i];  
    }  
    cout<<Max.val<<endl;  
    for (int i=0;i<Max.v.size();i++) cout<<Max.v[i]<<" ";  
    cout<<endl;  
    return 0;  
}  

Topics: C++ OpenJudge