POJ 3691:DNA repair【AC自动机+DP】

题目链接:http://poj.org/problem?id=3691


题目大意:

给出一些能导致癌症的DNA序列,一个已知序列的DNA片段,问至少得改多少个碱基对才能使这个DNA片段不会致癌。


多字符串匹配的话,那首先想到的当然是AC自动机,不过这题中,AC自动机又和别的不一样,因为在这里的trie树中,每个节点只可能有四个儿子节点(A、T、G、C),建立自动机的时候,任一节点都不能有空,就是说当从结点通过一个不能往下连接的字符时,孩子要通过j的fail结点来找到,或者回到根。而一般的AC自动机是不用建立该指针,直接放空即可。

建立了AC自动机之后,我们得到如下的DP方程:

dp[i][son]=min(dp[i][son],dp[i-1][j]+(str[i-1]!=k))

dp[i][j]中,i代表匹配到DNA片段的第i个字符,j表示当前在trie树上的节点,son为j的儿子节点。


代码:

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
#define N 1002
using namespace std;
const int kind=4;
const int INF=0x3f3f3f;

int allocp;
int verID[100];
int n,dp[N][N];
char word[25],str[N];

struct TrieNode
{
    int index;
    bool unsafe;
    TrieNode *next[kind];
    TrieNode *fail;
}memory[N],*q[N];

TrieNode *createTrieNode()
{
    TrieNode *p=&memory[allocp];
    p->index=allocp++;
    p->fail=NULL;
    p->unsafe=false;
    memset(p->next,0,sizeof(p->next));
    return p;
}
void insertTrieNode(TrieNode *pRoot,char s[])
{
    TrieNode *p=pRoot;
    for(int i=0;s[i];i++)
    {
        int k=verID[s[i]];
        if(p->next[k]==NULL)
            p->next[k]=createTrieNode();
        p=p->next[k];
    }
    p->unsafe=true;
}

void bulid_ac(TrieNode *pRoot)
{
    int head=0,tail=0;
    TrieNode *p,*temp;
    q[tail++]=pRoot;
    pRoot->fail=NULL;
    while(head<tail)
    {
        p=q[head++];
        for(int i=0;i<kind;i++)
        {
            if(p->next[i]!=NULL)
            {
                if(p==pRoot)
                    p->next[i]->fail=pRoot;
                else
                {
                    p->next[i]->fail=p->fail->next[i];
                    if(p->next[i]->fail->unsafe)
                        p->next[i]->unsafe=true;
                }
                q[tail++]=p->next[i];
            }
            else// 由于需要在安全节点上找,所以如果没有这个next也要
            {
                if(p==pRoot)
                    p->next[i]=pRoot;
                else
                    p->next[i]=p->fail->next[i];
            }
        }
    }
}

int main()
{
    verID['A']=0,verID['T']=1,verID['G']=2,verID['C']=3;
    int t=1;
    TrieNode *pRoot;
    while(~scanf("%d",&n),n)
    {
        allocp=0;
        pRoot=createTrieNode();
        while(n--)
        {
            scanf("%s",word);
            insertTrieNode(pRoot,word);
        }
        bulid_ac(pRoot);
        scanf("%s",str);
        int len=strlen(str);
        //DP部分,i表示当前匹配到第i位,j表示当前在trie树上的状态。
        for(int i=0;i<=len;i++)
            for(int j=0;j<allocp;j++)
                dp[i][j]=INF;
        dp[0][0]=0;
        TrieNode *p;
        for(int i=1;i<=len;i++)
        {
            for(int j=0;j<allocp;j++)
            {
                if(dp[i-1][j]!=INF)//之前更新过的才找
                {
                    p=&memory[j];
                    for(int k=0;k<kind;k++)
                    {
                        if(!p->next[k]->unsafe)//只搜安全节点
                        {
                            int son=p->next[k]->index;
                            dp[i][son]=min(dp[i][son],dp[i-1][j]+(verID[str[i-1]]!=k));//如果不同需要+1,改变成该点。
                        }
                    }
                }
            }
        }
        int ans=INF;
        for(int i=0;i<allocp;i++)
            if(!memory[i].unsafe && ans>dp[len][i])
                ans=dp[len][i];
    
        printf("Case %d: %d\n",t++,ans==INF?-1:ans);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值