阿里字符串匹配DP维度压缩

阿里“天池”竞赛平台近日推出了一个新的挑战任务:对于给定的一串 DNA 碱基序列 tt,判断它在另一个根据规则生成的 DNA 碱基序列 ss 中出现了多少次。

首先,定义一个序列 ww

\displaystyle w_{i} = \begin{cases}b, & i = 0\\(w_{i-1} + a) \mod n, & i > 0\end{cases}wi={b,(wi1+a)modn,i=0i>0

接下来,定义长度为 nn 的 DNA 碱基序列 ss(下标从 00 开始):

\displaystyle s_{i} = \begin{cases}A , & (L \le w_{i} \le R) \land (w_{i}\ \mathrm{mod}\ 2 = 0)\\T , & (L \le w_{i} \le R) \land (w_{i}\ \mathrm{mod}\ 2 = 1)\\G , & ((w_{i} < L) \lor (w_{i} > R)) \land (w_{i}\ \mathrm{mod}\ 2 = 0)\\C , & ((w_{i} < L) \lor (w_{i} > R)) \land (w_{i}\ \mathrm{mod}\ 2 = 1)\end{cases}si=A,T,G,C,(LwiR)(wi mod 2=0)(LwiR)(wi mod 2=1)((wi<L)(wi>R))(wi mod 2=0)((wi<L)(wi>R))(wi mod 2=1)

其中 \land 表示“且”关系,\lor 表示“或”关系,a\ \mathrm{mod}\ ba mod b 表示 aa 除以 bb 的余数。

现给定另一个 DNA 碱基序列 tt,以及生成 ss 的参数 n , a , b , L , Rn,a,b,L,R,求 tt 在 ss 中出现了多少次。

输入格式

数据第一行为 55 个整数,分别代表 n , a , b , L , Rn,a,b,L,R。第二行为一个仅包含ATGC的一个序列 tt

数据保证 0 < a < n,0<a<n, 0 \le b < n,0b<n, 0 \le L \le R < n,0LR<n, |t| \le 10^{6}t106a,na,n 互质。

对于简单版本,1 \leq n \leq 10^{6}1n106

对于中等版本,1 \leq n \leq 10^{9}, a = 11n109,a=1

对于困难版本,1 \leq n \leq 10^{9}1n109

输出格式

输出一个整数,为 tt 在 ss 中出现的次数。

样例说明

对于第一组样例,生成的 ss 为TTTCGGAAAGGCC

样例输入1
13 2 5 4 9
AGG
样例输出1
1
样例输入2
103 51 0 40 60
ACTG
样例输出2
5

思路就是简易字符串匹配的DP思路,如图:




由于2维度数组10^14次方直接越界,所以改成1维,特别注意维度压缩的时候列的遍历需要从后往前,这样才能保证不会覆盖混乱。

如果从前往后,会出现覆盖问题,如图所示:



所以需要改成从后往前的遍历j。改完之后的代码如下图所示,虽然优化了空间复杂度,但时间复杂度仍然是10*14次方,所以只能过11组数据,后面就是TLE。


#include<bits/stdc++.h>
using namespace std;
int t[1000007];
int w[1000007];
int s[1000007];
int dp1[1000007];

int main(void){
	int n,a,b,L,R;
	cin>>n>>a>>b>>L>>R;
	string ss;
	int len=0;
	cin>>ss;
	for(int i=0;i<ss.length();i++){
    if(ss[i]=='A'){
    	t[len++]=1;
    }
    else if(ss[i]=='T'){
    	t[len++]=2;
    }
    else if(ss[i]=='G'){
    	t[len++]=3;
    }
    else if(ss[i]=='C'){
    	t[len++]=4;
    }
	}

/*	
	for(int i=0;i<len-1;i++){
		cout<<t[i]<<"  ";
	}cout<<endl;
*/	

	for(int i=0;i<n;i++){
		if(i==0){
			w[i]=b;
		}
		else{
			w[i]=(w[i-1]+a)%n;
		}
	}
	for(int i=0;i<n;i++){
		if(L<=w[i]&&w[i]<=R&&w[i]%2==0){
			s[i]=1;
		}
		if(L<=w[i]&&w[i]<=R&&w[i]%2==1){
			s[i]=2;
		}
		if(((w[i]<L)||(w[i]>R))&& w[i]%2==0){
			s[i]=3;
		}
		if(((w[i]<L)||(w[i]>R))&& w[i]%2==1){
			s[i]=4;
		}
	    //cout<<s[i]<<"  ";
	}//cout<<endl;


	for(int i=0;i<ss.length();i++){
		for(int j=n-1;j>=0;j--){
		    if(t[i]==s[j]){
		    	if(i==0){
		    		dp1[j]=1;
		    	}
		    	else{
		    		if(dp1[j-1]==i){
		    			dp1[j]=max(dp1[j-1]+1,dp1[j]);
		    		}
		    		else{
		    			dp1[j]=max(dp1[j],0);
		    		}
		    	}
		    }
		}
	}
	
	int cnt1=0;
	for(int i=0;i<n;i++){
		if(dp1[i]==ss.length()){
			cnt1++;
		}
	}
	
	cout<<cnt1<<endl;
	return 0;
}

另外附上POW同学好心提供的造样例程序= =

#include <cstdio>
#include <cstdlib>
using namespace std;
int main ()
{
	freopen ("in.txt","w",stdout);
 //	srand (666072);
	printf ("1000000 ");
	int a=rand (),b=rand (),L=rand (),R=rand (),Lent=2; 
	printf ("%d %d %d %d\n",a,b,L,R);
	for (int i=1;i<=Lent;++i)
	{
		int tmp=rand ()%4+1;
		switch (tmp)
		{
			case 1:printf ("A");break;
			case 2:printf ("T");break;
			case 3:printf ("G");break;
			case 4:printf ("C");break;
		}
	}
	puts ("");
	return 0;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值