分析如DNA序列这样的生命科学数据是计算机的一个有趣应用。从生物学的角度上说,DNA 是一种由腺嘌呤、胞嘧啶、鸟嘌呤和胸腺嘧啶这 四种核苷酸 组成的链式结构。这四种核苷酸分别用大写字母 A、C、G、T 表示。这样,一条 DNA 单链可以被表示为一个只含以上四种字符的字符串。我们将这样的字符串称作一个 DNA 序列。
有时生物学家可能无法确定一条 DNA 单链中的某些核苷酸。在这种情况下,字符 N 将被用来表示一个不确定的核苷酸。换句话说,N 可以用来表示 A、C、G、T 中的任何一个字符。我们称包含一个或者多个 N 的 DNA 序列为未完成序列;反之,就称作完成序列。如果一个完成序列可以通过将一个未完成序列中的 每个 N 任意替换成 A、C、G、T 得到的话,就称完成序列适合这个未完成序列。举例来说,ACCCT 适合 ACNNT,但是 AGGAT 不适合。
研究者们经常按照如下方式排序四种核苷酸:A 优先于 C,C 优先于 G,G 优先于 T。如果一个 DNA 序列中的每个核苷酸都与其右边的相同或者优先,就将其归类为范式-1。举例来说,AACCGT 是范式-1,但是 AACGTC 不是。
一般来说,一个 DNA 序列属于范式-j ( j > 1),只要它属于范式-( j-1)或者是一个范式-( j-1)和一个范式-1的连接。举例来说,AACCC、ACACC和ACACA都是范式-3,但GCACAC和ACACACA不是。
同样,研究者们按照字典序对 DNA 序列进行排序。按照这个定义,最小的属于范式-3的DNA序列是AAAAA,最大的是TTTTT。这里是另外一个例子,考虑未完成序列ACANNCNNG。那么前 7 个适合这个未完成序列的 DNA 序列是:
ACAAACAAG
ACAAACACG
ACAAACAGG
ACAAACATG
ACAAACCCG
ACAAACCGG
ACAAACCTG
【任务】
写一个程序,找到按字典序的第 R 个适合给定的长度为 M 的未完成序列的 范式-K。
这个题我觉得很有意思。大意是要在一个DNA序列中定一些值,求这个模板串的第R大范式-k。
这里我们首先需要理解一下题意:范式-k代表什么?可以明白范式-2是具有一个转折的串,范式-3是具有至多两个转折的串,那么范式-k是具有至多k-1个转折的串。如果我们将ACGT分别映射为1, 2, 3, 4,将原字符串变为数字串str,那么一个转折可以定义为:
#include<cstdio>
using namespace std;
typedef long long LL;
const int NUM=50005;
int m,K,str[NUM];
LL R,f[NUM][5][15],Sum[NUM][5][15];
char In[NUM];
inline int Code(char a)
{ int ret;
//putchar(a);
switch(a)
{
case 'A':ret=1;break;
case 'C':ret=2;break;
case 'G':ret=3;break;
case 'T':ret=4;break;
case 'N':ret=0;break;
}
return ret;
}
inline char Decode(int a)
{ char ret;
switch(a)
{
case 1:ret='A';break;
case 2:ret='C';break;
case 3:ret='G';break;
case 4:ret='T';break;
}
return ret;
}
void Read()
{ int i;
scanf("%d%d%lld",&m,&K,&R);
K--;//表示转折点个数而非范式名
scanf("%s",In);
for(i=1;i<=m;i++)
str[i]=Code(In[i-1]);//putchar('\n');
//for(i=1;i<=m;i++)
// printf("%d",str[i]);putchar('\n');
}
void Preload()
{ int i,now,ik,lst,Dw,Up;
f[m+1][4][0]=1;//////////边界条件
for(i=m;i>=1;i--)
{ if(str[i])Dw=Up=str[i];
else{ Dw=1;Up=4;}
for(now=Dw;now<=Up;now++)
{ if(str[i]==now||str[i]==0)
{ for(lst=1;lst<now;lst++)
for(ik=1;ik<=K;ik++)
f[i][now][ik]+=f[i+1][lst][ik-1];
for(lst=now;lst<=4;lst++)
for(ik=0;ik<=K;ik++)
f[i][now][ik]+=f[i+1][lst][ik];
}
else
for(ik=0;ik<=K;ik++)
f[i][now][ik]=0;
}
for(now=1;now<=4;now++)
{ Sum[i][now][0]=f[i][now][0];
for(ik=1;ik<=K;ik++)
Sum[i][now][ik]=f[i][now][ik]+Sum[i][now][ik-1];
}
}//printf("PreloadDONE\n");
/* for(i=m;i>=1;i--) { printf("i=%d\n",i); for(now=1;now<=4;now++) { printf(" now=%d\n ",now); for(ik=0;ik<=K;ik++) printf(" %lld",Sum[i][now][ik]); putchar('\n'); } } */
}
void Solve()
{ int i,now;
str[0]=1;
for(i=1;i<=m;i++)
{ K--;//默认形成一个转折
for(now=1;now<=4;now++)
{ if(now==str[i-1])K++;//取消转折
if(Sum[i][now][K]>=R)break;
R-=Sum[i][now][K];
}
str[i]=now;
putchar(Decode(str[i]));
}
putchar('\n');
}
int main(){
Read();
Preload();
Solve();
return 0;
}