CodeForces 86C Genetic engineering (AC自动机 上 DP)

博客详细解析了CodeForces 86C问题,该问题涉及遗传工程背景下的DNA序列覆盖。通过AC自动机和动态规划(DP)的方法解决如何确保每个位置都能被给定基因片段覆盖的问题。文章讨论了转移状态、预处理以及避免计算重复的关键点,并给出了代码实现。

CodeForces 86C Genetic engineering

原题地址http://codeforces.com/problemset/problem/86/C

题意:
基因是一段有遗传效应DNA片段,我们认为这个片段仅由”A”,”T”,”C”,”G”组成,为了方便,我们只需考虑DNA的一条链。
一条DNA单链长度为n,给出m种基因片段(字符串)的集合,希望使得这条链上的每一个位置都受到可以被集合中的基因片段覆盖。注意有基因片段的序列可以相互重叠,一个位置被覆盖当且仅当它被至少一个集合中的序列包含。
问多少种满足条件的链,对1e9+9取模。

数据范围
1<=n<=1000,1<=m<=10 ,集合中的每个序列长度不超过10。

题解:
(今天的T1)
(我的第一道AC自动机上DP)
dp[n][i][k]表示当前作为DNA链上的第n个点,在AC自动机上的i节点,倒数有长度为k的序列还没有被覆盖。
预处理 val[i]= 点 i 表示的字符串可以覆盖的长度,例如 AGCT,在T对应的点的val值就是4。

那么转移就是:
v点是u点的子节点,此时转移dp[i][u][k],表示当前作为DNA链上的第i个点,在AC自动机上的u节点,倒数有长度为k的序列还没有被覆盖。
若此时 val[v]>=k+1 :
dp[i+1][v][0]+=dp[i][u][k]
(表示到当前点可以覆盖了。)
否则:
dp[i+1][v][j+1]+=dp[i][u][k]
(没覆盖的点又多一个)

能够转移到dp[i+1][v][0],只能转移到dp[i+1][v][0]。否则若遇到后面有个很长的串,两种情况就会都被计算。

我在考试时纠结的点在于,会不会有算重的情况:
eg:
AGCT
AGC
T
实际上,在AC自动机上,AGC的末尾并不会转到T,因为他本身有ch[T],
因此并不会直接从fail树上走到T。
如果每个单词节点都向上面的点连边,显然就会出现上述乱跳的情况。
而如果不连这些边,它也能够通过跳fail跳到应该跳的点。

代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<ctime>
#include<queue>
#define LL long long
using namespace std;
const int mod=1000000009;
int num(char c)
{
    if(c=='A') return 0;
    if(c=='G') return 1;
    if(c=='C') return 2;
    else return 3;
}
const int N=1010;
const int M=110;
int n,m,ch[M][4],fail[M],val[M],tail=0;
LL  dp[N][M][15],ans=0;
char s[20];
queue<int> Q;
void insert()
{
    int len=strlen(s);
    int tmp=0;
    for(int i=0;i<len;i++)
    {
        int c=num(s[i]);
        if(!ch[tmp][c]) ch[tmp][c]=++tail;
        tmp=ch[tmp][c];
    }
    val[tmp]=len;
}
void getfail()
{
    for(int i=0;i<4;i++)
    if(ch[0][i]){Q.push(ch[0][i]); fail[ch[0][i]]=0;}

    while(!Q.empty())
    {
        int top=Q.front(); Q.pop();
        for(int i=0;i<4;i++)
        {
            if(!ch[top][i]) {ch[top][i]=ch[fail[top]][i]; continue;}
            int u=ch[top][i];
            fail[u]=ch[fail[top]][i];
            val[u]=max(val[u],val[fail[u]]);
            Q.push(u);
        }
    }   
}
void getans()
{
    memset(dp,0,sizeof(dp));
    dp[0][0][0]=1;
    for(int i=0;i<n;i++)
    for(int u=0;u<=tail;u++)    
    for(int j=0;j<=10;j++)
    for(int c=0;c<4;c++)
    {
        int v=ch[u][c];
        if(val[v]>=j+1) dp[i+1][v][0]=(dp[i+1][v][0]+dp[i][u][j])%mod;
        else dp[i+1][v][j+1]=(dp[i][u][j]+dp[i+1][v][j+1])%mod;     
    }
    LL ans=0;
    for(int i=1;i<=tail;i++)
    ans=(ans+dp[n][i][0])%mod;

    printf("%I64d\n",ans);
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        scanf("%s",s);
        insert();
    }
    getfail();
    getans();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值