最长公共子序列lLCS/最长上升公共子序列LIS

最长公共子序列 LCS

1:找最长公共子序列的长度

考虑最长公共子序列问题如何分解成子问题,设A=“a0,a1,…,am-1”,B=“b0,b1,…,bm-1”,并Z=“z0,z1,…,zk-1”为它们的最长公共子序列。不难证明有以下性质:

(1) 如果am-1=bn-1,则zk-1=am-1=bn-1,且“z0,z1,…,zk-2”是“a0,a1,…,am-2”和“b0,b1,…,bn-2”的一个最长公共子序列;

(2) 如果am-1!=bn-1,则若zk-1!=am-1,蕴涵“z0,z1,…,zk-1”是“a0,a1,…,am-2”和“b0,b1,…,bn-1”的一个最长公共子序列;

(3) 如果am-1!=bn-1,则若zk-1!=bn-1,蕴涵“z0,z1,…,zk-1”是“a0,a1,…,am-1”和“b0,b1,…,bn-2”的一个最长公共子序列。

这样,在找A和B的公共子序列时,如有am-1=bn-1,则进一步解决一个子问题,找“a0,a1,…,am-2”和“b0,b1,…,bm-2”的一个最长公共子序列;如果am-1!=bn-1,则要解决两个子问题,找出“a0,a1,…,am-2”和“b0,b1,…,bn-1”的一个最长公共子序列和找出“a0,a1,…,am-1”和“b0,b1,…,bn-2”的一个最长公共子序列,再取两者中较长者作为A和B的最长公共子序列。

求解:

引进一个二维数组c[][],用c[i][j]记录X[i]与Y[j] 的LCS 的长度,b[i][j]记录c[i][j]是通过哪一个子问题的值求得的,以决定搜索的方向。
我们是自底向上进行递推计算,那么在计算c[i,j]之前,c[i-1][j-1],c[i-1][j]与c[i][j-1]均已计算出来。此时我们根据X[i] = Y[j]还是X[i] != Y[j],就可以计算出c[i][j]。

#include<cstring>
#include<cmath>
#include<cstring>
#include<cstdio>
const int qq=1e3+10;
char x[qq],y[qq];
int dp[qq][qq];
int main()
{
    while(~scanf("%s%s",x+1,y+1)){
        x[0]=y[0]='.';
        int len=strlen(x)>strlen(y)?strlen(x):strlen(y);
    //    printf("%d %d\n",strlen(x),strlen(y));
        for(int i=0;i<=len;++i)
            dp[i][0]=dp[0][i]=0;
        for(int j,i=1;i<strlen(x);++i)
            for(j=1;j<strlen(y);++j)
                if(x[i]==y[j])
                    dp[i][j]=dp[i-1][j-1]+1;
                else
                    dp[i][j]=dp[i-1][j]>dp[i][j-1]?dp[i-1][j]:dp[i][j-1];
        printf("%d\n",dp[strlen(x)-1][strlen(y)-1]);
    }
    return 0;
}

最长上升公共子序列 LIS

dp思路

dp[ i ]以序列中第i个元素结尾的最长上升子序列的长度

那么状态转移方程为:if (a[i] > a[j]) dp[i] = MAX (dp[i], dp[j] + 1);

复杂度为O(n*n)

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int main()
{
    int dps[10010],num[10010],n,i,j;
    while(~scanf("%d",&n))
    {
        for(i=1; i<=n; i++)
        {
            scanf("%d",&num[i]);
            dps[i]=1;
        }
        for(i=1; i<=n; i++)
            for(j=1; j<i; j++)
            {
                if(num[i]>num[j])
                    dps[i]=max(dps[i],dps[j]+1);
            }
        int ans=0;
        for(i=1; i<=n; i++)
        {
            ans=max(ans,dps[i]);
        }
        printf("%d\n",ans);
    }
    return 0;
}

最长上升子序列(LIS)的典型变形,熟悉的n^2的动归会超时。LIS问题可以优化为nlogn的算法。

定义 num[i]为原来的序列,a[i]为记录最长上升子序列,len是最长上升子序列的长度,a[len]是最长上升子序列中最末尾的元素,即最小的元素

最开始a[1]=num[1],len=1

然后判断如果num[i]>a[len],a[++len]=num[i]

    如果num[i]<=a[len], 在a中找到一个位置满足a[j-1]<num[i]<a[j],将长度为j最长上升子序列的最小末尾元素用num[i]更新掉

    a[j]=num[i];

最后输出长度len即可

这里可以用upper_bound函数来进行二分查找a[j]的位置

题目链接

http://acm.hdu.edu.cn/showproblem.php?pid=1950

#include<cstdio>
#include<algorithm>
using namespace std;
int main()
{
    int t,n,num[40010],a[40010],i;
    scanf("%d",&t);
    while(t--)
    {
    scanf("%d",&n);
    for(i=1;i<=n;i++)
        scanf("%d",&num[i]);
    int len=1;
    a[1]=num[1];
    for(i=2;i<=n;i++)
    {
        if(num[i]>a[len])
        {
            a[++len]=num[i];
            continue;
        }
        int tt=upper_bound(a+1,a+len+1,num[i])-a;
        a[tt]=num[i];
    }
    printf("%d\n",len);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/shufuyouqian/article/details/81184182