最长公共子序列(计数问题)

Posted kayiko

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了最长公共子序列(计数问题)相关的知识,希望对你有一定的参考价值。

题意:
字符序列的子序列是指从给定字符序列中随意地(不一定连续)去掉若干个字符(可能一个也不去掉)后所形成的字符序列。令给定的字符序列X=“x0,x1,…,xm-1”,序列Y=“y0,y1,…,yk-1”是X的子序列,存在X的一个严格递增下标序列<i0,i1,…,ik-1>,使得对所有的j=0,1,…,k-1,有xij = yj。
例如X=“ABCBDAB”,Y=“BCDB”是X的一个子序列。对给定的两个字符序列,求出他们最长的公共子序列长度,以及最长公共子序列个数。

一.求最长公共子序列的长度(基础LCS问题)
首先从动态规划的角度来考虑:
所有问题被分解为子问题,子问题套子问题。
1.确定研究状态:最长公共子序列的长度
那么每一层的问题就转化求该层的***最长公共子序列的长度***。
2.确定研究问题的划分层次(如何划分子问题)。(循环变量所代表的意义及状态边界问题):当s1和s2的长度均为1时,结果是很容易判断的,所有很自然的想到用长度来对问题进行划分。长度小的子问题被包含在长度大的子问题中作为数据被调用。——>用两层循环(i,j)来进行。
3.确定状态转移方程:(重中之重,因为状态转移方程是否正确决定难题的攻克与否)同时,也是解决问题的核心。那么对于长度为i的s1和长度为j的s2。该状态下的子问题结果:dp[i][j]=?。
可以知道的是:分情况讨论:
(1).当S1[i]==S2[j]时,dp[i][j]可有三个来源:
当第i个和第j个参与最长公共子序列①:dp[i-1][j-1]+1;
当第i个和第j个不参与最长公共子序列②:dp[i-1][j]和dp[i][j-1]。
因为:dp[i-1][j-1]<=dp[i][j-1]<=dp[i-1][j-1]+1 所以,最终的答案是:
dp[i][j]=dp[i-1][j-1]+1。
(2).当S1[i]!=S2[j]时,S1[i]与S2[j]不可能同时入选最长公共子序列,所以dp[i][j]只能从dp[i-1][j]和dp[i][j-1]中推得结果。根据 最长 的要求,和dp所储存的内容( s1的前i个字符组成的串和 s2的前j个字符组成的串所能形成的最长公共子序列的长度)得到:dp[i][j]=max(dp[i-1][j],dp[i][j-1]).
4.构造关键代码:

for (i=1;i<=s1.length();i++) 
for (j=1;j<=s2.length();j++) 
if (s1[i]==s2[j]) dp[i][j]=dp[i-1][j-1]+1; 
else dp[i][j]=max(dp[i-1][j],dp[i][j-1])

二.求最长公共子序列的方案数:
依照动态规划的过程:
1.选定分析状态——最长公共子序列的方案数
2.分析研究问题的划分层次——(子问题和最终最初一步)
显而易见:当长度为1时结果显而易见。
所以依照长度去研究子问题得到的循环变量以及划分这阶段的依据。
3.分析状态转移方程。
当循环变量至s1[i]和s2[j]时,状态转移方程为:
(首先我们能够得知,g[i][j]来源于g[i-1][j-1],g[i][j-1],g[i-1][j])
(1) 当s1[i]==s2[j]时,
(①即当s1[i]与s2[j]做出贡献时,方案数与g[i-1][j-1]一致) g[i][j]+=g[i-1][j-1] 。
(②即当s1[i]与s2[j]不做出贡献时),需要判定dp[i-1][j],dp[i][j-1]与dp[i][j]是否相等(出现不相等则意味着最长公共子序列的长度改变,那么方案数就不能被记入)。
而同时因为 dp[i][j]=dp[i-1][j-1]+1所以dp[i][j]不一定会等于dp[i-1][j]或dp[i][j-1]。
所以需要用if语句判断,如果相等再将g[i-1][j]和g[i][j-1]的方案书加上。
即关键代码:

if (s1[i]==s2[j])
        {
            dp[i][j]=dp[i-1][j-1]+1;
            if ((dp[i][j]==1)&&(g[i][j]==0)) ++g[i][j];
            (因为方案开头需要新增方案数,即从0到1的改变)
            g[i][j]+=g[i-1][j-1];
            (即当s1[i]与s2[j]做出贡献时,这种情况下g[i-1][j-1]被记入g[i][j])
            if (dp[i][j]==dp[i][j-1]) g[i][j]+=g[i][j-1];
            (倘若长度相等则意味着s1[i]与s2[j]没有被同时算入子序列时,
            仍然有长度等于dp[i][j]的方案,数量等于g[i][j-1],此时g[i][j-1]也要被记入g[i][j])
            if (dp[i][j]==dp[i-1][j]) g[i][j]+=g[i-1][j];
            (倘若长度相等则意味着s1[i]与s2[j]没有被同时算入子序列时,
            仍然有长度等于dp[i][j]的方案,数量等于g[i-1][j],此时g[i-1][j]也要被记入g[i][j])
            g[i][j]=g[i][j]%p;(数量过大时,需要取模)
            /*
            此处需要解释,g[i][j-1]和g[i-1][j]的方案数指代的方案完全不重合
            (s1[i]或s2[j]必然参与构成最长公共子序列)。
            因为如果dp[i][j]==dp[i-1][j]==dp[i-1][j-1]+1。
            
            假设:存在s1[i]与s2[j]均不参与组成最长公共子序列同时有合法方案
            则有dp[i-1][j]==dp[i-1][j-1]或dp[i][j-1]==dp[i-1][j-1]。
            
            因为在s1[i]与s2[j]均不参与构成时仍有合法方案,
            则所有参与构成最长公共子序列的元素均来自于s1的前i-1个字符和s2的前j-1个字符
            此时又有在s1[i]与s2[j]均不参与构成时仍有合法方案,
            则有dp[i][j]==dp[i-1][j]==dp[i-1][j-1] ==>矛盾
            
            反证:g[i][j-1]和g[i-1][j]的方案数指代的方案完全不重合
            */
        }

(2) 当s1[i]!=s2[j]时,g[i][j]只有两个来源①g[i-1][j]②g[i][j-1] (因为s1[i]与s2[j] 不能同时参与当前的最长公共子序列的组成)。
①s1[i]与s2[j]均不做出贡献时:(dp[i][j]==dp[i-1][j-1])
dp[i][j]==dp[i-1][j-1];g[i][j]+=g[i-1][j-1]。(因为均不做出贡献,所有参与构成最长公共子序列的元 素均来自于s1的前i-1个字符和s2的前j-1个字符)
②s1[i]做出贡献,而s2[j]不做出贡献时:(dp[i][j]==dp[i][j-1])
dp[i][j]==dp[i][j-1],同时确定g[i][j-1]包含 s1[i]做出贡献与s1[i]不做出贡献两种情况。
所以g[i][j]+=(g[i-1][j]-g[i-1][j-1])。
③s2[j]做出贡献,而s1[i]不做出贡献时:(dp[i][j]==dp[i-1][j])
dp[i][j]==dp[i][j-1],同时确定g[i-1][j]包含 s2[j]做出贡献与s2[j]不做出贡献两种情况。
所以g[i][j]+=(g[i][j-1]-g[i-1][j-1])。
则有关键代码:

if (s1[i]!=s2[j])
{
    dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
    if (dp[i][j-1]==dp[i][j]) g[i][j]+=g[i-1][j];
    if (dp[i-1][j]==dp[i][j]) g[i][j]+=g[i][j-1];
    if (dp[i-1][j-1]==dp[i][j]) g[i][j]-=g[i-1][j-1];//其实在s1[i]==s2[j]中也因当有这一步,
    //但是因为dp[i][j]==dp[i-1][j-1]+1;所以这个判断语句可以省略。 
}

附:当字符串的长度达到5000+时,内存需求非常大,这样我们需要使用滚动数组。
我们发现,每一次dp[i]与g[i]的答案都只与第i层和第i-1层的结果有关,所以前i-2的数据都不可能在被使用,运用滚动数组可以优化空间。

(确定状态(最后一步与子问题)=>递归调用=>保留递归结果优化解决方案,状态转移方程,初始条件和边界情况,计算顺序)

原文链接:https://blog.csdn.net/qq_35851866/article/details/104102182

以上是关于最长公共子序列(计数问题)的主要内容,如果未能解决你的问题,请参考以下文章

bzoj 2423: [HAOI2010]最长公共子序列dp+计数

LCS(最长公共子序列)

算法基础课题目时间

动态规划——最长公共子序列与最长公共子串 (含Python实现代码)

字符串最长公共子序列问题

最长公共子序列