APIO2008 脱氧核糖核苷酸DNA

题意


 分析如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,那么一个转折可以定义为:


对于两个相邻下标 i , i+1 ,若 str[i]>str[i+1] ,称为一次转折。


 这里对于要求的字符串有两个限制:字典序第R大,转折不能超过K-1。对于字典序,我们有很多处理方式。典型的有:
FJOI 最短路径树问题
ZOJ2599 Graduated Lexicographical Ordering
 恩,肯定和FJOI没有什么关系,这里我们想到数位动规的终极题目:Graduated Lexicographical Ordering。根据字典序定义数字串的大小比较,同时有一个奇奇怪怪的限制。恩那个题我至今没有敢去写……记搜党表示恐惧……
 书归正传。这里我们的要求和数位动规的思想比较像,要求某一个属性在定值以下,另一个属性具有枚举统计的性质:我们可以一位一位地枚举当前位填什么,如果可以事先算出来这一位填好之后之后位还能填多少种,我们就可以轻松算出来这里填的数是否符合要求啦!
 记搜党表示只能转递推,因为这时候递推的思路明显更清晰……现在让我们来想想递推需要一些什么。当前处理的位数、当前准备填的数、已经用的转折数,这三个是题目中可以直接读出来的信息。是否够了呢?答案是肯定的。
 我们设 f[i][now][ik] 表示填到第i位,第i位填now,将会用ik个转折的方案数(字符串总数),于是我们可以写出一个转移方程:
f[i][now][ik]={now1lst=1f[i+1][lst][ik+1]+4lst=nowf[i+1][lst][ik]0if s[i]=now||s[i]=0else
 边界条件可以自己推一下,我就不写出来了。
 事情还没完。注意到这里范式-k是转折点 k1 的,所以我们需要维护一个 f 的前缀和 sum
sum[i][now][ik]=p=1ikf[i][now][p]

 然后考虑解的构造:从高位到低位按字典序递增枚举当前值,如果算出来的当前值小于R,显然还需要继续构造,我们就让 R=Sum[i][now][K] ,直到找到大于R的地方。同时,K的限制让我们在构造的时候必须注意从小到大枚举字典序的时候要更改K的大小以适应限制。
需要注意的地方:
理解范式-k与转折点个数的关系理解逐位构造解的思想和原理

 这里是我借鉴的一些blog:
http://blog.sina.com.cn/s/blog_7e5d81d80100pzo6.html
http://www.docin.com/p-508754465.html




代码

#include
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;lstfor(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;
}

你可能感兴趣的:(题解,数位动规)