nyoj 36 最长公共子序列

来源:互联网 发布:什么是java工程师 编辑:程序博客网 时间:2024/05/16 06:04

动态规划很神奇,在网上看到一位大牛对该类问题的分析,很透彻,转过来吧得意

最长公共子序列是一个十分实用的问题,它可以描述两段文字之间的“相似度”,即它们的雷同程度,从而能够用来辨别抄袭。对一段文字进行修改之后,计算改动前后文字的最长公共子序列,将除此子序列外的部分提取出来,这种方法判断修改的部分,往往十分准确。简而言之,百度知道、百度百科都用得上。

算法

  动态规划的一个计算两个序列的最长公共子序列的方法如下:
  以两个序列 X、Y 为例子:
  设有二维数组 f[i,j] 表示 X 的 i 位和 Y 的 j 位之前的最长公共子序列的长度,则有:
  f[1][1] = same(1,1);
  f[i,j] = max{f[i-1][j -1] + same(i,j),f[i-1,j],f[i,j-1]}
  其中,same(a,b)当 X 的第 a 位与 Y 的第 b 位完全相同时为“1”,否则为“0”。
  此时,f[j]中最大的数便是 X 和 Y 的最长公共子序列的长度,依据该数组回溯,便可找出最长公共子序列。
  该算法的空间、时间复杂度均为O(n^2),经过优化后,空间复杂度可为O(n)。


动态规划法

经常会遇到复杂问题不能简单地分解成几个子问题,而会分解出一系列的子问题。简单地采用把大问题分解成子问题,并综合子问题的解导出大问题的解的方法,问题求解耗时会按问题规模呈幂级数增加。

为了节约重复求相同子问题的时间,引入一个数组,不管它们是否对最终解有用,把所有子问题的解存于该数组中,这就是动态规划法所采用的基本方法。

考虑最长公共子序列问题如何分解成子问题,设A=“a0,a1,…,am-1”,B=“b0,b1,…,bn-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”的一个最长公共子序列;(肯定是,因为最后一个若相等,则该字符必在LCS中)

(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的最长公共子序列。

题目链接点击打开链接

AC代码:

#include<iostream>#include<string>#include<cstring>const int INF=1005;using namespace std;int _dp[INF][INF];string s1,s2;void dp(){    memset(_dp,0,sizeof(_dp));    for(int i=0;i<s1.length();i++)        for(int j=0;j<s2.length();j++)        {            if(s1[i]==s2[j]) _dp[i+1][j+1]=_dp[i][j]+1;            else _dp[i+1][j+1]=_dp[i+1][j]>_dp[i][j+1]?_dp[i+1][j]:_dp[i][j+1];        }}int main(){    int t;    cin>>t;    while(t--)    {        cin>>s1>>s2;        dp();        cout<<_dp[s1.length()][s2.length()]<<endl;    }    return 0;}