线性DP之 LIS_LCS_LCIS


引言

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

L I S ( L o n g e s t − I n c r e a s i n g − S u b s e q u e n c e ) \mathcal{LIS(Longest- Increasing -Subsequence)} LIS(LongestIncreasingSubsequence)

1,朴素做法

  • 状态表示:f[i]表示从第一个数字开始算,以w[i]结尾的最大的上升序列。(以w[i]结尾的所有上升序列中属性为最大值的那一个)
  • 状态计算(集合划分): j ∈ ( 0 , 1 , 2 , . . , i − 1 ) \mathcal{j∈(0,1,2,..,i-1)} j(0,1,2,..,i1) w [ i ] > w [ j ] \mathcal{w[i] > w[j]} w[i]>w[j]时, f [ i ] = m a x ( f [ i ] , f [ j ] + 1 ) \mathcal{f[i] = max(f[i], f[j] + 1)} f[i]=max(f[i],f[j]+1)
  • 有一个边界,若前面没有比i小的,f[i]1(自己为结尾)。
  • 最后在找f[i]的最大值

2,贪心做法(更优)

  • 状态表示:f[i]表示长度为i的最长上升子序列,末尾最小的数字
    (长度为i的最长上升子序列所有结尾中,结尾最小的) 即长度为i的子序列末尾最小元素是什么
  • 特性:随着i的递增,f[i]必然是递增的,这个性质不证自明
  • 接下来我们对我们需要的对每一个a[i]尝试去接入一个序列,当然,结尾必然比它小,但是我还想长度尽可能长
  • f[i]具有单调性,直接二分就可以找到第一个结尾小于或者等于a[i] 的序列了,好的,接特!!
  • 接完了,新的序列长度是接入序列的长度+1,取原来这么长的序列的结尾的数和这次的新构序列的最小值就成(后面还接嘞)
int n, a[N], f[N];
int lis()
{
    scanf("%d", &n);   for (int i = 0; i < n; i ++) scanf("%d", &a[i]);
    int len = 0;
    f[0] = -2e9;
    长度是0的结尾值是-2e9(表示长为0的子序列不存在)
    
    for (int i = 0; i < n; i ++)
    {
        f[len]表示长度是len的最长(严格)上升子序列,其结尾的值是  f[len]
        找到最接近但小于(小于等于)a[i] 的 f[k], 其中k 在 0 ~ i - 1, 这里的k表示子序列的长度, 结尾的值是f[k]
        也就是f[k] < a[i], f[k]表示子序列倒数第二个数
        
        int l = 0, r = len;
        while (l < r)
        {
            int mid = (l + r + 1) >> 1;
            if (f[mid] < a[i]) l = mid;
            else r = mid - 1;
        }
        
        len = max(len, r + 1);
        这一步很费解,当找到可以接入的序列时,r只能停留在已知的贪心序列中间,+1也不影响已知序列的长度
        如果贪心序列全都很符合要求的,说明贪心数组长度又增1,`r+1`新添一个元素已知的贪心序列的长度也加上1
       
        长度是r + 1的(严格)上升子序列是以 a[i] 结尾的
        f[r + 1] = a[i];
    }
    return len;
}

模式化:

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

L C S ( L o n g e s t − C o m m o n − S u b s e q u e n c e ) \mathcal{LCS(Longest- Common-Subsequence)} LCS(LongestCommonSubsequence)
特征:是指求出两个序列的最长公共子序列的问题

状态划分: f [ i , j ] \mathcal{f[i,j]} f[i,j]即以a[i] , b[i]结尾的子串的最大长度
状态转移:很明显,对于a[i] , b[j]来讲,只有选,或者不选这两种可能,用状态机的思路,0为选,1为不选

分四种情况:
1,00,都不选,转移自 f [ i − 1 , j − 1 ] \mathcal{f[i-1,j-1]} f[i1,j1]
2,01,选b[j]不选a[i],来自 f [ i − 1 , j ] \mathcal{f[i-1,j]} f[i1,j]
3,10,不选b[j]a[i],来自 f [ i , j − 1 ] \mathcal{f[i,j-1]} f[i,j1]
4,特殊情况,11,都能选,仅当b[j]==a[i]才可以,来自 f [ i − 1 , j − 1 ] + 1 \mathcal{f[i-1,j-1]+1} f[i1,j1]+1

注意:以及可能这三种情况具有交集,因为还是没用状态机,不过我们只要最值,多算几次没啥

int lcs()
{
    cin>>n>>m;
    scanf("%s \n %s", a+1, b+1);   
    小细节注意一下,scanf读的char0开始带数据,如果用在转移上,(0-1=-1)越界,会出现不得了的东西
    
        for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= m; j ++ )
        {
            f[i][j]=max(f[i-1][j],f[i][j-1]);
            if(a[i]==b[j])f[i][j]=max(f[i][j],f[i-1][j-1]+1);
        }
    return f[n][m];
}

三、LCIS问题(最长公共上升子序列)

参考:y总超强分析,绝绝子,速速读下
思路:考虑直接把两个问题并起来
回顾:这两个子问题的状态表示:
1,LCS, f [ i ] 表 示 以 i \mathcal{f[i]表示以 i } f[i]i结尾的上升串的最大长度
2,LIS, f [ i , j ] 表 示 在 a [ 1 , i ] , b [ 1 , j ] \mathcal{f[i,j]表示在a[1,i] , b[1,j] } f[i,j]a[1,i],b[1,j]中出现的最长公共串的长度
如果两个串是公共的的话,那么求a的上升子串和求b的上升子串是一样的
所有我们需要把末尾元素确定下来,人为规定一下就好

得出这次的状态: f [ i , j ] 表 示 在 a [ 1... i ] 和 b [ 1... j ] \mathcal{f[i,j]表示在a[1...i]和b[1...j]} f[i,j]a[1...i]b[1...j]公共的且以b[j]结尾的上升串的最大长度
状态转移:
1,不包含a[i],说明以b[j]结尾的公共串在扩入a[i]后,结尾数字没变,长度也没变,那么最长就是是f[i-1][j]了,传承下来就行

2,包含a[i],说明公共串长度和末尾数值全变了,那就有必要处理一下了

  • 尝试接入b串中b[j]前的所有元素之后

1,朴素代码(生硬套模板)

int lcis()
{
    int ans=-1e9;
    for (int i = 1; i <= n; i ++ )
    {
        for (int j = 1; j <= n; j ++ )
        {
            f[i][j]=f[i-1][j];         不管如何,不选a[i]都是可以的
            if(a[i]==b[j])             当串更新了,我们处理一下
            {
                maxv=1;                 
                决定选b[j]那最起码有一个了
                
                for (int k = 1; k < j; k ++ )
                if(b[k]<a[i])maxv=max(f[i-1][k]+1,maxv);   依次尝试接入
                
                f[i][j]=max(f[i][j],maxv);
                ans=max(ans,f[i][j]);
            }
        }
    }
    return ans;   
}

2,前缀和思路优化

  • 冗余就在最内层尝试接串的循环上
  • b[k]比对的对象始终不变,那么把k循环和j循环合并,maxv可以继承下来其实就行了
int lcis()
{
    int ans=-1e9;
    for (int i = 1; i <= n; i ++ )
    {
        maxv=1;
        for (int j = 1; j <= n; j ++ )
        {
            f[i][j]=f[i-1][j];
            if(a[i]==b[j])f[i][j]=max(f[i][j],maxv),ans=max(ans,f[i][j]);
            if(b[j]<a[i])maxv=max(f[i-1][j]+1,maxv);
        }
    }
    return  ans;    
}

tips – hack:
最长公共上升子序列抽串在判断的数据失误,最长公共子序列不唯一
like this:
6 5 8 7 6 3 1
8 6 7 6 3 1

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

流苏贺风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值