奇异的病毒问题

题目源自《程序员》2001年第8期编程擂台

问题描述

最近出现了一种奇异的生物病毒,这种病毒侵染的范围很广,科学家们经研究发现,这种病毒的内部存在一种环状DNAD片断,而正常情况下,生物的基因总是呈线状排列的.

因此据Dr.X推测,病毒侵染某个生物的几率可能正是与此有关:被侵染生物的DNA中总是或多或少存在着一些片段,它们与环状DNA片断中的一部分是相同的(可称之为匹配”),而它们中最长的片断越长,生物被侵染的可能性就越大,其后又有研究发现,某些生物被侵染的几率远大于其它生物,对此Dr.X推测,可能是因为环状DNA片段不仅可以部分地匹配被侵染生物的DNA片断,还可以循环地匹配.

例如如果环状DNA片断为abc(也可以表示为bca或是cab,但它和acb是不同的),被侵染生物的DNAabbcabcabb,那么能被环状DNA片断匹配的最长片段就是bcabcab,长度为7.

面对大量的实验数据,Dr.X希望你能够帮她设计一个程序,计算出被侵染生物的DNA中与环状DNA片段匹配的最长片段的长度.

输入格式:输入文件名为Virus.in,文件第1行是一个正整数n(n<=1000),表示环状DNA片段的长度.2行是一长度为n的字符串(由大小写英文字母组成,下同),它描述了环状DNA片段.3行是一个正整数m(m<=100000),表示被侵染生物的DNA的长度,4行是一长度为m的字符串,它描述被侵染生物的DNA.

输出格式:输出文件名为Virus.out,文件只需包含一个数,即最长片段的长度.

样例输入 Virus.in          样例输出 Virus.out

3                                  7

abc

10

abbcabcabb

      

算法分析:

       除去背景信息,不难看出本题的求解目标是一个环串A(即环状DNA片段)和一个线串B(即被侵染生物的DNA)的最长公共子串.

       本题可以用动态规划求解:我们用T[i][j]来表示以线串B的第i个字符和环串A的第j个字符结尾的最长公共子串的长度,显然有如下公式:

                     T[i-1][j-1]+1;     A[j]=B[i] 1<j<=n

T[i][j]=   T[i-1][n]+1;       A[j]=B[i]  j=1

                  0;                   A[j]!=B[i] 

   这也是本题的动态转移方程

     例如在样例中,环串Aabc,线串Babbcabcabb,T如下表所示(空格表示0)      

 

j     i

1

2

3

4

5

6

7

8

9

10

1

1

 

 

 

3

 

 

6

 

 

2

 

2

1

 

 

4

 

 

7

1

3

 

 

 

2

 

 

5

 

 

 

 

       当然在编程实现时,我们是不会用一个100000*1000的表格来动态规划的,由于T[i]只与T[i-1]有关,所以可以保存一个2*1000的表格,空间复杂度为O(n),时间复杂度为O(n*m).另外,逐个拿A[j]B[i]比较也没有必要,由于字符集为{a,….z,A,……Z},并不太大,所以参考程序用一个索引表记录了每个字符在A中出现的位置,读入B[i]时直接定位即可

#include<iostream>

#include<cstring>

#include<string>

using namespace std;

const int MAX_N=1000;

int m,n,longest=0;

int firstPos[128];

int nextPos[MAX_N];

int dpTable[2][MAX_N];

void init()

 {  

     string st;

     memset(firstPos,-1,sizeof(firstPos));

     cin>>n;

     cin.get();

     cin>>st;  //读入数据并建立索引

     for(int i=n-1;i>=0;i--)

      {

          char ch=st[i];

          nextPos[i]=firstPos[ch];

          firstPos[ch]=i;

      }   

    

 }  

 void solve()

  {

      memset(dpTable,0,sizeof(dpTable));

      cin>>m;

      char prech=0;

      for(int i=0;i<m;i++) //动态规划

       {

           int now=i%2;

           int pre=1-now;

           char ch;

           cin>>ch;

           int pos=firstPos[ch];

           while(pos>=0)

            {

                if(pos==0)//动态规划的状态转移方程

                  dpTable[now][pos]=dpTable[pre][n-1]+1;

               else

                  dpTable[now][pos]=dpTable[pre][pos-1]+1;  

             

              if(dpTable[now][pos]>longest)

                 longest=dpTable[now][pos];

             pos=nextPos[pos];

            }  

          pos=firstPos[prech];//为下一次循环作初始化

          while(pos>=0)

           {

             dpTable[pre][pos]=0;

             pos=nextPos[pos]; 

           } 

          prech=ch;       

       }  

     cout<<longest<<endl;  

  }    

int main()

{

  

 

    init();

    solve();

    return 0;

}   

上面的程序中用到了一些建立字符索引的小技巧,其中firstPos数组和nextPos数组用来记录某个字符在病毒DNA中出现的位置,对于一个字符ch,首先根据pos=firstPos[ch]可以得到该字符在病毒DNA中的第一个位置,然后根据pos=nextPos[pos]可以得到下一个位置,pos=-1时说明已经完全遍历了该字符在病毒DNA中出现的所有位置.Solve函数中, for(int i=0;i<m;i++)循环用来遍历被侵染生物的DNA的每个字符,dpTable是一个2*n的表格,用来保存动态规划的信息.now表示当前阶段在dpTable中的行,pre表示上一个阶段在dpTable中的行,循环中首先得到被侵染生物的DNA的一个字符ch,然后根据firstPos nextPos依次遍历该字符在病毒DNA中的每个位置,并且根据状态转移方程计算对于状态的最优解,For 循环的最后几行:

pos=firstPos[prech];//为下一次循环作初始化

          while(pos>=0)

           {

             dpTable[pre][pos]=0;

             pos=nextPos[pos]; 

           } 

          prech=ch;       

prech始终是上一次循环时读入的字符ch,这几行的作用就是将dpTable的第pre行全部清0,这样下次循环时dpTable的第now行就已经初始化为0.

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值